前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >python实战案例

python实战案例

作者头像
苏州程序大白
发布2022-05-27 14:55:48
3.4K0
发布2022-05-27 14:55:48
举报
文章被收录于专栏:用户8907256的专栏

爬虫简单入
爬虫合法性-君子协议
  • 关于爬虫的合法性,有君子协议 在网站网址后加上/robots.txt查看君子协议
准备注意事项
  • 做爬虫前尽量不要使用任何网络代理,否则容易出现莫名的问题
手刃一个小爬虫(request模块实现)
  • 简单试做:将百度搜索源码爬取:
代码语言:javascript
复制
#百度
#需求:用程序模拟浏览器,输入一个网址,从该网址中获取到资源或者内容
from urllib.request import urlopen      #从包中导入模块

url="http://www.baidu.com"          #准备网址
resp = urlopen(url)            #用urlopen模拟浏览器打开网址,将返回的响应存入resp

"""
先print(resp.read())查看返回的内容
从中找到编码格式,一般为charset后位置
再进行解码

print(resp.read().decode("utf-8"))         #resp.read()从响应中读取内容,并用decode解码
"""

with open("D:\desktop\代码\python测试\Mywebsite.html",mode="w",encoding="utf-8") as web:            #打开名为"Mywebsite.html"的文件,模式为w写入,as语句将其简称为web,设置encoding打开编码
    web.write(resp.read().decode("utf-8"))              #resp.read()从响应中读取内容,并用decode解码,将其写入到上述文件

Web 请求、HTTP 协议、抓包

Web 请求过程解析
  • 1.服务器渲染:在服务器直接把数据和 html 整合在一起,统一返回给浏览器。 举例:浏览器向百度服务器发送请求,百度返回 html 页面源代码;在百度里搜索关键词,百度在服务器将关键词有关数据写入 html 页面源代码中,一并返回给浏览器
  • 2.客户端渲染:第一次请求只要一个 html 骨架,第二次请求拿到数据,进行数据展示。在页面源代码中,看不到数据。 举例:例如豆瓣电影排行榜的分类筛选网页,浏览器先向服务器请求,服务器返回 html 骨架(不包含数据),浏览器第二次请求,服务器返回数据,浏览器将 html 骨架与数据渲染结合,呈现页面。在源代码处搜索呈现的数据,无法找到。
  • 熟练使用浏览器抓包工具: Chrome 浏览器右键检查或者 F12,上方大类选择 Network; 刷新页面,此时所有返回的请求都在此处显示。点击文件可以打开源代码,通常第一个文件为网页骨架; Headers 中 Request URL 写有 url 地址,Preview 可以查看预览效果。在这些文件中通过预览找到和页面内容匹配的数据,回到 Headers 即可找到数据 url
  • 想要得到数据无需骨架,对于爬虫而言,目的为得到数据,骨架无影响
HTTP 协议
  • HTTP 协议基本概念
    • 协议:两台计算机之间为了能流畅的进行沟通而设置的一个君子协定,常见的协议有 TCP/IPSOAP 协议,HTTP 协议,SMTP 协议等
    • HTTP 协议:Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web)服务器传输超文本到本地浏览器的传输协议。直白点儿,浏览器和服务器之间的数据交互遵守的就是 HTTP 协议
    • HTTP 协议把一条消息分为 三大块内容 ,无论是请求还是响应都是三块内容
      • 请求 : 1、请求行 → 请求方式(get/post),请求 url 地址,协议 2、请求头 → 放一些服务器要使用的附加信息 3、请求体 → 一般放一些请求参数
      • 响应 : 1、状态行 → 协议,状态码 2、响应头 → 放一些客户端要使用的附加信息 3、响应体 → 服务器返回的真正客户端要用的内容(HTML,json 等)
  • 抓包工具及获得的重要信息
    • Network-Headers-General:一般信息 Request URL:URL 地址 Request Method:请求方式 Status Code:状态码
    • Network-Headers- Response Headers : 响应头 cookie:本地字符串数据信息(用户登录信息,反爬的 token) 其他:各种神奇的莫名其妙的字符串(这个需要经验,一般都是 token 字样,防止各种攻击和反爬)
    • Network-Headers- Request Headers : 请求头 User-Agent:请求载体的身份标识(用啥发送的请求,如浏览器信息) Referer:防盗链(这次请求是从哪个页面来的,反爬需要) cookie:本地字符串数据信息(用户登录信息,反爬的 token)

    附:请求方式

    • Get:显示提交(常用于搜索,通常只读)
    • Post:隐式提交(常用于对数据增删改,通常可写入)

requests 模块入门
模块安装
  • requests 模块为第三方支持库,需要手动安装
代码语言:javascript
复制
pip install requests
Requests 入门-1

GET 请求:将搜狗搜索内容爬取,并学习简单的反爬

代码语言:javascript
复制
import requests

url = "https://www.sogou.com/web?query=周杰伦"          #保存网址字符串给变量,中文可能转码错误,手动打上去
#第10行处被拦截,可以将更多请求头信息补入,定义一个字典headers,将User-Agent写入字典,User-Agent通过抓包网页骨架中的Request Headers(请求头)找到,注意直接复制后Mozilla前会多一个空格,记得删除
dict = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36"}

#用get请求方式请求url,所有地址栏中的url都是get方式请求,将响应存入resp。第四行信息补充完成后,将字典写入headers参数,处理简单的反爬
resp = requests.get(url,headers=dict)

#print(resp)         #打印resp,返回网页状态码,返回200正常
print(resp.text)        #打印页面源代码,但爬虫被拦截了,前往第四行补充信息

resp.close()            #关闭请求

可以进行一些小修改,做到更改搜索对象:

代码语言:javascript
复制
import requests

#手动输入搜索的内容
query=input("输入你要搜索的内容:")
#利用f-string,做到搜索内容更改
url = f"https://www.sogou.com/web?query={query}"          #保存网址字符串给变量,中文可能转码错误,手动打上去
#第10行处被拦截,可以将更多请求头信息补入,定义一个字典headers,将User-Agent写入字典,User-Agent通过抓包网页骨架中的Request Headers(请求头)找到,注意直接复制后Mozilla前会多一个空格,记得删除
dict = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36"}

#用get请求方式请求url,所有地址栏中的url都是get方式请求,将响应存入resp。第四行信息补充完成后,将字典写入headers参数,处理简单的反爬
resp = requests.get(url,headers=dict)

#print(resp)         #打印resp,返回网页状态码,返回200正常
print(resp.text)        #打印页面源代码,但爬虫被拦截了,前往第四行补充信息

resp.close()            #关闭请求
Requests 入门-2

POST 请求:爬取百度翻译的结果

代码语言:javascript
复制
"""
打开百度翻译后按F12进入抓包工具,清除多余的文件,注意输入法切换为英文,输入英文单词后,翻译框下方有一个小列表
在抓包工具中通过preview预览尝试寻找列表的数据文件,发现sug文件为数据文件
打开sug文件的Headers,获取需要的信息:url地址,请求方式为POST
打开Payload,找对From Data,为POST传参数据,对于上个GET程序中利用f-string传入参数的方式就不灵了
"""
import requests

url = "https://fanyi.baidu.com/sug"         #准备url,注意url为数据的url,即sug文件Headers的url

word = input("请输入你要翻译的英文:")        #准备翻译的单词
dat = {"kw":word}               #由于POST传参数据来源为From Data,所以按照From Data中的格式,将搜索数据改写入字典,此时可以通过变量更改数据

resp = requests.post(url,data=dat)             #由于网页访问方式为POST,故使用POST访问,将dat传入data参数,即传入From Data。将响应存入resp
#print(resp.text)               #输出发现文件有乱码,可以另外直接输出json文件
print(resp.json())                #将服务器返回的内容直接处理成json(),按照python字典方式输出

resp.close()            #关闭请求

#总结,对于POST请求,发送的数据必须放在字典中,通过data参数进行传递
Requests 入门-3

浏览器渲染的二次 GET

  • 通常网站 url 里有问号”?”,问号前的是 url,问号后的是参数
代码语言:javascript
复制
"""
豆瓣电影分类排行榜网页通过浏览器渲染,有两次数据传递
在抓包工具中选择筛选XHR类别(常表示二次请求数据),找到跟页面差不多的蕴含量大一些的XHR文件,就是页面的数据文件找到数据文件Headers:
查看url,通常网站url里有问号"?",问号前的是url,问号后的是参数,查看请求方式为GET方式
在Payload中有Query String Parameters(url问号后参数),
"""
import requests

url = "https://movie.douban.com/j/chart/top_list"        #参数过长,可以重新封装url参数,url问号后参数部分可以删除

#重新封装参数。将抓包Query String Parameters的参数复制进字典,分别打双引号,加逗号
param = {
    "type": "24",
    "interval_id": "100:90",
    "action":"" ,
    "start": "0",
    "limit": "20"
}

header = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36"}        #布置user-agent

resp = requests.get(url,params=param,headers=header)              #将param的参数传入params(截止params)。将返回的响应存入resp
#print(resp.request.url)         #输出按照参数重组后的url地址
#print(resp.text)                #code 0,什么都没有,说明被反爬了
print(resp.json())

resp.close()            #关闭请求

#反爬处理

#首先尝试修改user-agent
#print(resp.request.headers)         #(补充)查看默认信息,user-agent
#获取浏览器抓包user-agent,准备(第20行),写入requests.get的参数
#成功拿到数据,但有乱码,将24行优化为25行,获取json文件
  • 在豆瓣中下拉,刷新出新的电影,同时 Query String Parameters 中出现新的数据,与原数据对比发现只有 Query String Parameters 的 start 参数变化,可以借此修改代码中 start 参数实现新效果

数据解析

数据解析概述
Re 解析_正则表达式
  • Re 解析:Regular Expression 的简写,正则表达式,一种使用表达式的方式对字符串进行匹配的语法规则
  • 我们抓取到的网页源代码本质上就是一个超长的字符串。,想从里面提取内容,用正则表达式再合适不过了
  • 优点:速度快效率高准确性高 缺点:新手上手难度较大
  • 不过只要掌握了正则编写的的逻辑关系,写出一个提取页面内容的正则并不复杂
  • 正则的语法:使用元字符进行排列组合用来匹配字符串 在线测试正则表达式https://tool.oschina.net/regex/
  • 元字符:具有固定含义的特殊符号
  • 常用元字符
  • 量词:控制前面的元字符出现的次数
  • 贪婪匹配惰性匹配

这两个着重说一下,写爬虫用的最多的就是惰性匹配 *?表示尽可能少的让*匹配东西

Bs4 解析_HTML 语法
  • Bs4 解析:Beautiful Soup4 的简写,简单易用的 HTML 解析器,需要掌握一些 HTML 语法
  • HTML(Hyper Text Markup Language)超文本标记语言,是编写网页最基本最核心的语言,其语法就是用不同的标签,对网页上的内容进行标记,从而使网页显示不同的效果,简单举例:
代码语言:javascript
复制
<h1>I Love You</h1>
  • 常用标签
  • 属性:标签内后跟的控制标签行为属性,其后所写的为属性值,简单举例:
代码语言:javascript
复制
<h1 align="right">I Love You</h1>

借此实现标题文字右对齐,其中,align为属性,right为属性值

  • 由此,HTML基本语法格式为:
代码语言:javascript
复制
<标签 属性="值" 属性="值">被标记的内容</标签>
Xpath 解析_XML 概念
  • Xpath 解析:XML 解析器,用来提取XML 文档中的节点,Xpath 是在 XML 文档中搜索的一门语言。HTML 是 XML 的一个子集
  • 基础概念
代码语言:javascript
复制
<book>
  <id>1</id>
  <name>野花遍地香</name>
  <price>1.23<price>
  <author>'
    <nick>周大强</nick>
    <nick>周芷若</nick>
  </author>
</book>

在上述 html 中:

  1. book,id,name,price等都被称为节点
  2. id,name,price,author被称为book子节点book被称为他们的父节点
  3. id,name,price,author被称为同胞节点

python 实现 Re 解析
Python 的 re 模块使用

在 python 中使用正则表达式,可以使用re模块,re模块记住几个常用功能就足够我们日常使用了:

代码语言:javascript
复制
import re           #引入re模块

#findall:匹配字符串中所有的符合正则的内容
list = re.findall("\d+","我的电话号是10086,我朋友的电话是10010")           #findall的结果是一个列表
print(list,"\n")

#列表效率低下,面对大量数据难以应对,按如下处理
#finditer:匹配字符串中所有的内容[返回的是迭代器],从迭代器中遍历拿到内容需要.group()函数
it = re.finditer("\d+","我的电话号是10086,我朋友的电话是10010")
#print(it)
for i in it:
    print(i.group())
print()

#search返回的结果是match对象,那数据需要.group(),此外search全文检索,检索到一个就直接返回
s = re.search("\d+","我的电话号是10086,我朋友的电话是10010")
#print(s)
print(s.group(),"\n")

#match从头开始匹配,可以认为默认在正则前加了^符号,如下方10086前加一个非数字,则匹配为空
a = re.match("\d+","10086,我朋友的电话是10010")
print(a.group(),"\n")

#compile预加载正则表达式,能够提高一定的运行效率
obj = re.compile("\d+")
#此时obj即预加载\d+的正则,下次使用可以obj.函数,如下:
ret = obj.finditer("我的电话号是10086,我朋友的电话是10010")
#print(ret)
for it in ret:
    print(it.group())
print()

#用正则表示全部文本信息
s="""
<div class='jay'><span id='1'>雷军</span></div>
<div class='jj'><span id='2'>李彦宏</span></div>
<div class='jolin'><span id='3'>张小龙</span></div>
<div class='sylar'><span id='4'>马云</span></div>
<div class='tory'><span id='5'>马化腾</span></div>
"""
obj1 = re.compile("<div class='.*?'><span id='\d+'>.*?</span></div>",re.S)            #re.S作用:让点.能匹配换行符
#<div class='部分都一样,后面不一样部分.*?代替,匹配后jay双引号后部分一样,一直到id=后单引号后不同,
#用\d或\d+或者.*?代替,后同理,略
result = obj1.finditer(s)
for it in result:
    print(it.group())
print()

#从全部文本信息中提取想要的信息(在上述代码中修改)
#在要提取的文本.*?等正则符号处,用小括号包住,小括号前写?P<组名> ,最后在遍历的组括号(60行)写入这个组名
obj1 = re.compile("<div class='.*?'><span id='\d+'>(?P<gualudeng>.*?)</span></div>",re.S)            #re.S作用:让点.能匹配换行符
#<div class='部分都一样,后面不一样部分.*?代替,匹配后jay双引号后部分一样,一直到id=后单引号后不同,
#用\d或\d+或者.*?代替,后同理,略
result = obj1.finditer(s)
for it in result:
    print(it.group("gualudeng"))
print()
手刃豆瓣 top250

代码语言:javascript
复制
#数据在页面源代码中
#思路:拿到页面源代码,通过re正则提取我们想要的有效信息
from email import header
import requests,re,csv

url = "https://movie.douban.com/top250"
ua = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36"}
resp = requests.get(url,headers=ua)             #简单的提取源代码和反反爬
#print(resp.text)            #检查页面源码
page_content = resp.text            #保存源代码至变量

#解析数据
#正则表达式定位,建议找需要数据的上几层标签做定位
#<li>为上层标签,换行时的空白可能是换行可能是空格,使用.*?表示,继续匹配到下一行,后面多行都用.*?匹配,直接找到需要的title,在需要部分单独列组(),补充后面的截止部分(此处截止至</span>处),后略
obj = re.compile('<li>.*?<div class="item">.*?<span class="title">(?P<title>.*?)</span>.*?<p class="">.*?<br>(?P<year>.*?)&nbsp.*?<span class="rating_num" property="v:average">(?P<score>.*?)</span>',re.S)               #编写正则方法

#使用finditer进行正则筛选
result = obj.finditer(page_content)
#遍历result,得到数据
for it in result:
    print("\n电影名:",it.group("title"),"\n年份:",it.group("year").strip(),"\n评分:",it.group("score"))            #group中的名字均为正则中的组名, .strip()为去除空白(空格)

#将数据存入文件,建议存储为csv格式。引入csv模块,.csv文件默认以逗号进行数据分割
f = open("data.csv",mode="w",encoding="utf-8")          #打开文件data.csv,没有文件自动创建,模式为r写入,打开格式为utf-8
csvwriter = csv.writer(f)               #创建csvwriter,写入数据时写入f文件,注意写入数据格式应为字典
result = obj.finditer(page_content)     #同18行
for it in result:
    dic= it.groupdict()                 #创建字典,将上述20-21行数据整理进字典
    dic["year"] = dic["year"].strip()           #单独处理需要去掉空格的year组
    csvwriter.writerow(dic.values())              #writerow为写入一行函数,括号()内为写入数据,写入的为字典的数据.values()
f.close()                               #关闭文件
print("over!")

#目前完成了top25的整理,而翻页数据只需要修改url后的参数即可,比如第二页url为https://movie.douban.com/top250?start=25&filter=
#由此得第一页参数start=0,第三页start=50,所以输出top250排行榜,可以此为方向研究

参考源代码:

屠戮盗版天堂电影信息 补充 html 中 a 标签超链接知识
代码语言:javascript
复制
"""
1、确认数据在页面源码中,定位到2022必看热片
2、从2022必看热片中提取到子页面链接地址
3、请求子页面的链接地址,拿到想要的下载地址
"""
  • 实际操作
代码语言:javascript
复制
import requests,re

main_url = "https://dytt89.com/"            #主界面url
child_url_list = []
#原老版网站存在https加密,requests模块也有安全验证,所以会报错,可以使用verify=False关闭安全验证来解决,运行时最上部的警告意为“请求没有进行安全验证”。新版网站已取消
resp = requests.get(main_url , verify=False)          #verify=False关闭安全验证
resp.encoding = "gb2312"            #指定字符集编码
#print(resp.text)            #输出乱码,需要重新编码,网页编码格式通常在源码charset处会写明,找到后补充上一行代码,更改默认编码
#定位提取ul里面的li
obj1 = re.compile('2022必看热片.*?<ul>(?P<ul>.*?)</ul>',re.S)           #提取需要的部分
obj2 = re.compile("<a href='(?P<href>.*?)'",re.S)                       #提取a标签中的url链接

#开始筛选提取
result1 = obj1.finditer(resp.text)             #第一次提取板块源码部分
for it in result1:
    ul = it.group("ul")                         #存入ul
    #print(ul)              #检验输出
    #html知识补充:在html中,a标签表示超链接,如:<a href='url'>周杰伦</a>,网页上显示周杰伦的超链接,跳转地址为href=后的url
    #提取子页面链接(href后url)
    result2 = obj2.finditer(ul)             #第二次从板块源码部分提取url,但提取的url为参数,需要与main_url拼接
    for itt in result2:
        add = itt.group("href")                 #存入add
        #print(add)              #检验输出
        child_url = main_url + add.strip("/")              #拼接url,使用strip除去拼接处多余的一个/符号
        #print(child_url)            #检验输出
        child_url_list.append(child_url)            #将网址保存进列表里(注意空列表已经提前定义)

#提取子页面内容
obj3 = re.compile('◎片  名(?P<movie>.*?)<br />.*?<td style="WORD-WRAP: break-word" bgcolor="#fdfddf"><a href="(?P<download>.*?)">',re.S)
for url in child_url_list:
    child_resp = requests.get(url)           #操作基本同上
    child_resp.encoding = "gb2312"
    #下两行仅为测试使用
    #print(child_resp.text)
    #break
    result3 = obj3.search(child_resp.text)
    print(result3.group("movie"))
    print(result3.group("download"))

参考源代码:

python 实现 Bs4 解析

Python 的 bs4 模块使用

python 的 bs4 模块为第三方模块,需要先安装,安装 cmd 语法如下:

代码语言:javascript
复制
pip install bs4
抓取示例:北京新发地菜价(已失效,仅可参考)

:页面重构,下示例代码仅可参考,无法运行,网站改为浏览器渲染,使用 POST 请求

代码语言:javascript
复制
# 页面源代码中能找到数据,所以直接爬取,后使用bs4提取数据即可
import requests
import csv
from bs4 import BeautifulSoup
url = "http://www.xinfadi.com.cn/marketanalysis/0/list/1.shtml"
resp = requests.get(url)
# print(resp.text)           #测试

# 准备需要写入的文件
f = open("菜价.csv", mode="w")
csvwriter = csv.writer(f)

# 解析数据,把页面源代码交给beautiful soup处理,生成bs4的对象
page = BeautifulSoup(resp.text, "html.parser")  # 括号第二个参数指定html解析器

# 从bs4对象查找数据(find / find_all(标签 属性="值"))
# 查找内容。由于class是python关键字,所以写class_代替
table = page.find("table", class_="hq_table")
# print(table)           #测试

# 得到的是表格,表格内每一行为tr标签,每一行内每列为td标签
# 再次筛选tr,拿到所有数据行,做切片,从1行开始切,去除0行的表头
trs = table.find_all("tr")[1:]
for tr in trs:              # 每一行的数据进行遍历
    tds = tr.find_all("td")              # 拿到每行中的所有td
    name = tds[0].text              # .text表示拿到被标签标记的内容
    low = tds[1].text
    avg = tds[2].text
    high = tds[3].text
    kind = tds[4].text
    set = tds[5].text
    date = tds[6].text
    # print(name, low, avg, high, kind, set, date)           #输出测试
    csvwriter.writerow([name, low, avg, high, kind, set, date])  # 写入文件,需要列表
f.close()
print("over")

参考源代码:

抓取优美图库的图片**(已失效,仅可参考)

代码语言:javascript
复制
# 1.拿到主页面的源代码,然后提取到子页面的链接地址,href
# 2.通过href拿到子页面的数据内容,提取图片的下载地址,img->src
# 3.下载图片

import requests
import time         # 对应37行代码
from bs4 import BeautifulSoup

url = "https://umei.cc/bizhitupian/weimeibizhi"
resp = requests.get(url)
resp.encoding = "utf-8"          # 解码处理
# print(resp.text)            #测试

# 把源代码交给bs4
main_page = BeautifulSoup(resp.text, "html.parser")
# 取得typelist后提取a标签
alist = main_page.find("div", class_="TypeList").find_all("a")
# print(alist)            #测试
for a in alist:             # 循环遍历每一个a标签
    # print(a.get("href"))        #测试,直接通过get就可以得到属性值
    href = a.get("href")
    # 至此任务1完成。进行任务2

    # 拿到子页面源代码
    child_resp = requests.get(href)
    child_resp.encoding = "utf-8"
    child_page_text = child_resp.text
    # 从子页面中拿到图片的下载路径
    child_page = BeautifulSoup(child_page_text, "html.parser")
    p = child_page.find("p", align="center")
    img = p.find("img")
    src = img.get("src")

    # 下载图片
    img_resp = requests.get(src)
    # img_resp.content         # content获取到的是字节,写回到文件就是图片
    img_name = src.split("/")[-1]        # 图片命名,对src链接以"/"切割,并取最后一部分命名
    with open(img_name, mode="wb") as f:          # wb写入二进制图片
        f.write(img_resp.content)       # 写入图片
    print("part success!", img_name)
    time.sleep(1)           # 防止访问过于频繁被封ip,休息1秒钟
print("all over!")

参考源代码:

python 实现 Xpath 解析

Python 的 lxml 模块使用

python 的 lxml 模块为第三方模块,需要先安装,安装 cmd 语法如下:

代码语言:javascript
复制
pip install lxml
python 中 xpath 解析的使用
代码语言:javascript
复制
from lxml import etree

xml = """......"""          # 将XML文档存入变量,(此处省略,本程序无法直接运行)
tree = etree.XML(xml)       # 生成etree的XML文档
# result = tree.xpath("/book")        # xpath查找book节点,"/"表示层级关系,第一个"/"是根节点
result1 = tree.xpath("/book/name/text()")        # text()表示获取被标记的内容
print(result1)
# 双斜杠"//"表示范围内跨层级搜索(全局搜索)
result2 = tree.xpath("/book/author//nick/text()")
# xpath中"*"符号为通配符,表示任意,同正则表达式的"."
result3 = tree.xpath("/book/author/*/nick/text()")

requests 模块进阶

requests 进阶概述
  • 我们在之前的爬虫中其实已经使用过headers了,header为 HTTP 协议中的请求头,一般存放一些和请求内容无关的数据,有时也会存放一些安全验证信息,比如常见的User-Agenttokencookie
  • 通过requests发送的请求,我们可以把请求头信息放在 headers 中.也可以单独进行存放,最终由 requests 自动帮我们拼接成完整的 http 请求头
处理 cookie 模拟浏览器登录
防盗链的处理
代理
抓包工具的补充使用
  • 浏览器抓包工具中Initiatorrequest call back项记录了网站调用的 JS 栈,从下往上按时间顺序排列。点击可以进入 JS 源码,点击窗口左下方的大括号可以对源码进行缩进排版,找到需要的发送行设置断点,利用断点调试找到需要的信息,可以借此得到一些网站的加密过程或其他源码(涉及逆向 JS,较为复杂)

线程与进程

基础概念
  • 进程:操作系统运行程序时,会为其开辟一块内存空间,专门用于存放与此程序相关的数据,这块内存区域称为xxx 进程
  • 线程:在xxx 进程中存在多个线程,共同完成工作
  • 进程资源单位线程执行单位。每一个进程至少要有一个线程,且程序执行时会有一个主线程,即可认为启动一个程序默认会有一个主线程
多线程
  • 举例单线程
代码语言:javascript
复制
def func():
    for i in range(1000):
        print("func", i)

if __name__ == "__main__":
    func()
    for j in range(1000):
        print("main", j)

一个简单的线性单线程程序,主函数中,func函数执行完毕后才会执行主函数的for循环

  • 多线程示例 1,直接利用Thread类:
代码语言:javascript
复制
from threading import Thread            # 导入线程的类

def func():
    for i in range(1000):
        print("func", i)

if __name__ == "__main__":
    t = Thread(target=func)             # 创建一个线程类的对象,并且target=告诉程序这个线程执行的话会执行谁,为线程安排任务
    t.start()                           # 设置多线程状态为可以执行状态,具体的执行时间由CPU决定
    for j in range(1000):
        print("main", j)
  • 多线程示例 2,利用面向对象的特性,自己写一个继承Threadclass类:
代码语言:javascript
复制
from threading import Thread

class MyThread(Thread):         # 继承Thread
    def run(self):          # 固定的        # 当线程被设置可以执行之后,被执行的就是run()
        for i in range(1000):
            print("子线程", i)

if __name__ == "__main__":
    t = MyThread()
    # t.run()         #千万不能使用t.run,否则就是类方法的调用!--->单线程
    t.start()
    for j in range(1000):
        print("主线程", j)
  • 多线程的函数传参
代码语言:javascript
复制
from threading import Thread

def func(name):
    for i in range(1000):
        print(name, i)

if __name__ == "__main__":
    # 在Thread函数中,添加args进行传参,且args接收的数据类型必须是元组
    # 注意,元组内只有一个元素的时候需要加逗号
    t1 = Thread(target=func, args=("周杰伦",))
    t1.start()
    t2 = Thread(target=func, args=("王力宏",))
    t2.start()
多进程
  • 相对于多线程而言,多进程会开辟新的空间增加占用,所以平常使用机会不大,多半可以由多线程代替
  • 多进程示例(基本与多线程类似):
代码语言:javascript
复制
from multiprocessing import Process

def func():
    for i in range(1000):
        print("子进程", i)

if __name__ == "__main__":
    p = Process(target=func)
    p.start()
    for j in range(1000):
        print("主进程", j)
线程池与进程池
  • 线程池一次性开辟一些线程,用户直接给线程池提交任务,可以节省开辟线程的资源,线程任务的调度交给线程池来完成
  • 进程池:同上线程池类似
  • 线程池示例
代码语言:javascript
复制
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
# 注:ThreadPoolExecutor为线程池,ProcessPoolExecutor为进程池,按需引入

def fn(name):
    for i in range(1000):
        print(name, i)

if __name__ == "__main__":
    # 创建线程池
    # 创建一个由50个线程组成的线程池,其中ThreadPoolExecutor的参数控制线程数量
    with ThreadPoolExecutor(50) as t:
        # 表示100个任务的for循环
        for i in range(100):
            # 给线程提交任务
            t.submit(fn, name=f"线程{i}")
    # 等待线程池的内容全部完成才会执行(守护)
    print("over")

协程与异步

协程概念
  • 样例理解
代码语言:javascript
复制
import time

def func():
    print("123")
    time.sleep(3)           # 让当前线程处于阻塞状态,CPU不为此程序工作
    print("456")

if __name__ == "__main__":
    func()
# 执行input()时,程序也是处于阻塞状态
# requests.get()请求等待过程中,程序也是处于阻塞状态
# 一般情况下,当程序处于IO操作时,线程都会处于阻塞状态
  • 协程:当程序遇见IO操作的时候,可以选择性的切换到其他任务在微观上是一个任务一个任务的进行切换在宏观上我们能看见的是多个任务一起共同执行 这种操作称为多任务异步操作 上方所讲的一切,都是在单线程的条件下
多任务异步协程
  • 语法理解
代码语言:javascript
复制
import asyncio
# 用async定义异步协程函数

async def func():
    print("你好,我叫塞丽娜")

if __name__ == "__main__":
    #此时的函数是异步协程函数,此时函数执行得到的是一个协程对象
    g = func()
    # print(g)            # 测试
    asyncio.run(func)           # 协程程序运行需要asyncio模块的支持
  • 基本语法 1
代码语言:javascript
复制
import asyncio
import time

async def func1():
    print("你好,我叫潘金莲")
    # time.sleep(3)           # 当程序出现同步操作时,异步就中断了
    await asyncio.sleep(3)            # 异步模块的sleep,使用await挂起,切到其他任务
    print("你好,我叫潘金莲")

async def func2():
    print("你好,我叫王建国")
    # time.sleep(2)
    await asyncio.sleep(2)
    print("你好,我叫王建国")

async def func3():
    print("你好,我叫李雪琴")
    # time.sleep(4)
    await asyncio.sleep(4)
    print("你好,我叫李雪琴")

if __name__ == '__main__':
    f1 = func1()
    f2 = func2()
    f3 = func3()
    # 将需要执行的任务放入列表
    tasks_ = [
        f1, f2, f3
    ]
    t1 = time.time()          # 记录执行前的时间
    # 一次性启动多个任务(协程)
    asyncio.run(asyncio.wait(tasks_))
    t2 = time.time()          # 记录执行后的时间
    print(t2-t1)            # 输出运行时间
  • 基本语法 2(推荐)
代码语言:javascript
复制
import asyncio

async def func1():
    print("你好,我叫潘金莲")
    await asyncio.sleep(3)
    print("你好,我叫潘金莲")

async def func2():
    print("你好,我叫王建国")
    await asyncio.sleep(2)
    print("你好,我叫王建国")

async def func3():
    print("你好,我叫李雪琴")
    await asyncio.sleep(4)
    print("你好,我叫李雪琴")

async def main():
    # 第一种写法
    # f1 = func1()
    # await f1            #await通常放在协程对象前面
    # 后略
    #第二种写法(推荐)
    tasks_=[
        func1(),func2(),func3()
    ]
    await asyncio.wait(tasks_)

if __name__ == '__main__':
    asyncio.run(main())
  • 注意:Python3.8后会报警告,由于版本迭代,Python3.11后将不再支持await asyncio.wait()中直接传入协程对象,而是需要将协程对象通过asyncio.create_task()转换为asyncio.Task对象,才能使用,如下:
代码语言:javascript
复制
import asyncio

async def func1():
    print("你好,我叫潘金莲")
    await asyncio.sleep(3)
    print("你好,我叫潘金莲")

async def func2():
    print("你好,我叫王建国")
    await asyncio.sleep(2)
    print("你好,我叫王建国")

async def func3():
    print("你好,我叫李雪琴")
    await asyncio.sleep(4)
    print("你好,我叫李雪琴")

async def main():
    tasks_=[
        asyncio.create_task(func1()),
        asyncio.create_task(func2()),
        asyncio.create_task(func3())
    ]
    await asyncio.wait(tasks_)

if __name__ == '__main__':
    asyncio.run(main())

异步 http 请求

概述
代码语言:javascript
复制
- 发送请求时,原先的`requests.get()`是一个同步操作,会将异步程序转为同步,需要换成**异步请求操作**
Python 的 aiohttp 模块使用

python 的 aiohttp 模块为第三方模块,需要先安装,安装 cmd 语法如下:

代码语言:javascript
复制
pip install aiohttp
基本语法-下载网站图片
代码语言:javascript
复制
import asyncio
import aiohttp

urls = [
    "photo_url1",
    "photo_url2",
    "photo_url3"
]

async def aiodownload(url):
    # 发送请求,得到图片内容,保存到文件
    # 得到异步会话
    # s = aiohttp.ClientSession()         # 等价于原来的requests模块,例如可使用s.get
    # 使用with上下文管理器,可以省去手动session.close()
    async with aiohttp.ClientSession() as session:
        # 发送请求,得到resp
        async with session.get(url) as resp:
            # 此处为读取图片,所以使用content
            # resp.content.read()         # 等价于原先的resp.content,此处需要多.read()读取
            # resp.text()               # 读取文本,需要多一个()
            # resp.json()               # 同原先相同
            # 写入文件
            with open("文件名(可以按url地址切割存入变量)", mode="wb") as f:
                # IO操作,需要await挂起
                f.write(await resp.content.read())

async def main():
    tasks_ = []
    for url in urls:
        tasks_.append(aiodownload(url))
    await asyncio.wait(tasks_)

if __name__ == '__main__':
    asyncio.run(main())
示例:百度小说《西游记》

代码语言:javascript
复制
# 所有章节内容的url(章节名称,cid)
# https://dushu.baidu.com/api/pc/getCatalog?data={"book_id":"4306063500"}
# 章节内部的具体内容
# https://dushu.baidu.com/api/pc/getChapterContent?data={"book_id":"4306063500","cid":"4306063500|1569782244","need_bookinfo":1}

import requests
import asyncio
import aiohttp
import aiofiles
import json

# 1.同步操作,访问getCatalog,拿到所有章节的cid和名称
# 2.异步操作:访问getChapterContent,拿到小说内容并下载

async def aiodownload(cid, book_id, title):
    # 准备参数,作为json代码
    data = {
        "book_id": f"{book_id}",
        "cid": f"{book_id}|{cid}",
        "need_bookinfo": 1
    }
    # 转换json为字符串
    data = json.dumps(data)
    # 拼接url
    url = f"https://dushu.baidu.com/api/pc/getChapterContent?data={data}"
    # 准备异步请求
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            dic = await resp.json()
            # dic["data"]["novel"]["content"]           #小说的内容
            # 利用aiofiles异步写入文件
            async with aiofiles.open(f"D:\Desktop\代码程序\python测试\新建文件夹\{title}.txt", mode="w", encoding="utf-8") as f:
                await f.write(dic["data"]["novel"]["content"])

async def getCatalog(url):
    resp = requests.get(url)
    # print(resp.text)          # 测试
    dic = resp.json()
    # 准备异步的空列表
    tasks_ = []
    for item in dic["data"]["novel"]["items"]:              # item对应每个章节的名称和cid
        title = item["title"]
        cid = item["cid"]
        # print(cid,title)          # 测试
        # 每一个cid就是一个url,准备异步任务
        tasks_.append(aiodownload(cid, book_id, title))
    await asyncio.wait(tasks_)

if __name__ == '__main__':
    # 将book_id提取为单独的变量备用
    book_id = "4306063500"
    url = 'https://dushu.baidu.com/api/pc/getCatalog?data={"book_id":"' + book_id + '"}'
    asyncio.run(getCatalog(url))

selenium 模块

selenium 引入概念
代码语言:javascript
复制
from selenium.webdriver import Chrome

# 1.创建浏览器对象
web = Chrome()
# 2.打开一个网址
web.get("http://www.baidu.com")
print(web.title)
selenium 基础操作
  • 示例:抓取拉钩网站
代码语言:javascript
复制
from selenium.webdriver import Chrome
from selenium.webdriver.common.keys import Keys

import time
web = Chrome()
web.get("http://lagou.com")
# 点击页面中的某个元素,通过在页面检查元素,复制xpath
el = web.find_element_by_xpath('//*[@id="changeCityBox"]/p[1]/a')          # 找到元素
el.click()              # 点击元素

# 也可以直接写为web.find_element_by_xpath('//*[@id="changeCityBox"]/p[1]/a').click()
# 可以通过by后不同的查找方式查找,如div标签这种页面中存在很多的元素,可以通过find_elements全部获取
# web.find_elements_by_tag_name("div")

# 防止刷新速度慢,暂停1秒
time.sleep(1)

# 找到输入框,输入python ---> 输入回车/点击搜索
# 此处实现输入回车,找到输入框,使用.send_keys()输入内容
# 键盘回车通过第二行的包中的Keys模块实现,点进Keys可以查看所有能实现的键盘按键
web.find_element_by_xpath('//*[@id="search_input"]').send_keys("python", Keys.ENTER)

time.sleep(1)

# 查找存放数据的位置,进行数据提取(注:此处代码由于网页重构已失效,无法运行!)
# 找到存放数据的所有li,注意获取多个最后li的[]索引要删除
li_list = web.find_elements_by_xpath('//*[@id="s_position_list"]/ul/li')
for li in li_list:
    job_name = li.find_element_by_tag_name("h3").text
    job_price = li.find_element_by_xpath("./div/div/div[2]/div/span").text
    company_name = li.find_element_by_xpath("./div/div[2]/div/a").text
    print(job_name, company_name, job_price)
窗口之间的切换
  • 示例 1:抓取拉钩网站工作详情
代码语言:javascript
复制
from selenium.webdriver import Chrome
from selenium.webdriver.common.keys import Keys
import time

web = Chrome()
web.get("http://lagou.com")
web.find_element_by_xpath('//*[@id="changeCityBox"]/p[1]/a').click()
time.sleep(0.5)
web.find_element_by_xpath('//*[@id="search_input"]').send_keys("python", Keys.ENTER)
time.sleep(0.5)

# 点击岗位查看详情,会跳转到新页面
# 点击岗位
web.find_element_by_xpath('//*[@id="jobList"]/div[1]/div[1]/div[1]/div[1]/div[1]/a').click()

# 如何进入到新窗口进行提取
# 注意,即使浏览器已经切换新窗口,在selenium的眼中,新出现的窗口默认是不切换的(未被选中)
# 切换窗口,使用window_handles[-1]选中最后一个窗口选项卡
web.switch_to.window(web.window_handles[-1])

# 在新窗口中提取内容
job_detail = web.find_element_by_xpath('//*[@id="job_detail"]/dd[2]/div').text
print(job_detail)
# 关闭窗口
web.close()
# 重新调整窗口焦点
web.switch_to.window(web.window_handles[0])
  • 示例 2:处理 iframe 标签,示例站点 91 看剧
代码语言:javascript
复制
from selenium.webdriver import Chrome

web = Chrome()
# 页面中遇到iframe怎么处理
web.get("https://www.91kanju.com/vod-play/541-2-1.html")
# 要处理iframe,必须先得到iframe,然后切换视角到iframe,才能拿数据
# 定位到iframe
iframe = web.find_element_by_xpath('//*[@id="player_iframe"]')
# 切入窗口视角到iframe
web.switch_to.frame(iframe)
# 切出窗口视角到默认窗口视角
# web.switch_to.default_content()
tx = web.find_element_by_xpath('//*[@id="main"]/h3[1]').text
print(tx)
无头浏览器、下拉菜单 select 的处理、拿到 elements 页面源码
  • 无头浏览器:对于爬虫而言,浏览器的显示界面可以隐藏
  • 示例:艺恩电影排行
代码语言:javascript
复制
from selenium.webdriver import Chrome
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.select import Select
import time

# 无头浏览器
# 准备好配置参数
opt = Options()
# 添加参数
opt.add_argument("--headless")          # 无头
opt.add_argument("--disable-gpu")           # 禁用gpu
# =================================================================

# 在Chrome()中参加无头参数
web = Chrome(options=opt)
web.get("https://endata.com.cn/BoxOffice/BO/Year/index.html")

# 网址中有select下拉列表元素,如何处理
# 定位到下拉列表
sel_el = web.find_element_by_xpath('//*[@id="OptionDate"]')
# 对元素进行包装,包装成下拉菜单,需要引入第二行的包
sel = Select(sel_el)
# 让浏览器进行调整选项
# sel.options下拉框的列表的长度作为for循环次数,i就是每一个下拉框选项的索引位置
for i in range(len(sel.options)):
    # select_by-xxx处,by_index为按照索引进行切换,by_value为按照select选项value进行切换,by_visible_text为按照所见文本进行切换
    sel.select_by_index(i)          # 按照索引i进行切换
    time.sleep(2)           # 等待切换
    # 提取数据,此处省略

# =================================================================
# 如何拿到页面源代码Elements数据(经过数据加载以及JS执行之后的结果的html内容)
print(web.page_source)
处理验证码

‘//*[@id=“player_iframe”]’)

切入窗口视角到iframe

web.switch_to.frame(iframe)

切出窗口视角到默认窗口视角

web.switch_to.default_content()

tx = web.find_element_by_xpath(‘//*[@id=“main”]/h3[1]’).text print(tx)

代码语言:javascript
复制
##### **无头浏览器、下拉菜单 select 的处理、拿到 elements 页面源码**

- **无头浏览器**:对于爬虫而言,浏览器的显示界面可以隐藏
- **示例:艺恩电影排行**
  [艺恩电影排行(网址已失效)](https://endata.com.cn/BoxOffice/BO/Year/index.html)

```python
from selenium.webdriver import Chrome
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.select import Select
import time

# 无头浏览器
# 准备好配置参数
opt = Options()
# 添加参数
opt.add_argument("--headless")          # 无头
opt.add_argument("--disable-gpu")           # 禁用gpu
# =================================================================

# 在Chrome()中参加无头参数
web = Chrome(options=opt)
web.get("https://endata.com.cn/BoxOffice/BO/Year/index.html")

# 网址中有select下拉列表元素,如何处理
# 定位到下拉列表
sel_el = web.find_element_by_xpath('//*[@id="OptionDate"]')
# 对元素进行包装,包装成下拉菜单,需要引入第二行的包
sel = Select(sel_el)
# 让浏览器进行调整选项
# sel.options下拉框的列表的长度作为for循环次数,i就是每一个下拉框选项的索引位置
for i in range(len(sel.options)):
    # select_by-xxx处,by_index为按照索引进行切换,by_value为按照select选项value进行切换,by_visible_text为按照所见文本进行切换
    sel.select_by_index(i)          # 按照索引i进行切换
    time.sleep(2)           # 等待切换
    # 提取数据,此处省略

# =================================================================
# 如何拿到页面源代码Elements数据(经过数据加载以及JS执行之后的结果的html内容)
print(web.page_source)

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-05-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 切入窗口视角到iframe
  • 切出窗口视角到默认窗口视角
  • web.switch_to.default_content()
相关产品与服务
云服务器
云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档