爬虫-Requests模块
一、requests模块基本使用
1.1 get请求爬取静态页面数据
import requests
#1.爬取搜狗页面
#涉及到的知识点:参数动态化,UA伪装,乱码的处理
word = input("enter a key word:")
url = "https://www.sogou.com/web"
#参数动态化:将请求参数封装成字典作用到get方法的params参数中
params = {
"query":word
}
#UA伪装
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36"
}
response = requests.get(url=url,params=params,headers=headers)
response.encoding = "utf-8" #解决中文乱码问题
page_text = response.text
# page_text = response.json() #json返回的是序列好的对象
# img_data = response.content #content返回的是bytes类型的响应数据
fileName = word+".html"
with open(fileName,"w",encoding="utf-8") as fp:
fp.write(page_text)
print(word,"下载成功!!!")
1.2 post请求
import requests
#想要获取所有页码对应的位置信息
url = "http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword"
headers = {
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36"
}
for pageNum in range(1,8):
data = {
"cname": "",
"pid": "",
"keyword": "北京",
"pageIndex": str(pageNum),
"pageSize": "10",
}
#参数:data是用来实现参数动态化,等同于get方法中的params参数的作用
response = requests.post(url=url,headers=headers,data=data)
page_text = response.json()
for dic in page_text["Table1"]:
pos = dic["addressDetail"]
print(pos)
1.3 爬取示列
- 需求:爬取药监总局中的企业详情数据,每一家企业详情页对应的详情数据(爬取前5页企业)
- url:http://125.35.6.84:81/xk/
- 分析:
- 企业详情数据是否为动态加载数据?
- 基于抓包工具进行局部搜索。发现为动态加载数据
- 捕获动态加载的数据
- 基于抓包工具进行全局搜索。
- 定位到的数据包提取的
- url:
- http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsById
- http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsById
- 请求参数:
- id: 536878abac734332ae06dcb1a3fbd14a
- id: 950d66fbf8714fbc9e799010e483d2d5
- url:
- 结论:每一家企业详情数据对应的请求url和请求方式都是一样的,只有请求参数id的值不一样。
- 如果我们可以将每一家企业的id值捕获,则就可以将每一家企业详情数据进行爬取。
- 捕获企业的id
- 企业的id表示的就是唯一的一家企业。我们就猜测企业id可能会和企业名称捆绑在一起。
- 在首页中会有不同的企业名称,则我们就基于抓包工具对首页的数据包进行全局搜索(企业名称)
- url:http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsList
- 方式:post
- 请求参数:
- on=true&page=1&pageSize=15&productName=&conditionType=1&applyname=&applysn=
- 企业详情数据是否为动态加载数据?
#捕获多页数据
#获取每一家企业的id值,去首页分析查找对应企业的id值
url = "http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsList"
headers = {
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36"
}
for page in range(1,6):
data = {
"on": "true",
"page": str(page),
"pageSize": "15",
"productName": "",
"conditionType": "1",
"applyname": "",
"applysn": "",
}
response = requests.post(url=url,headers=headers,data=data)
all_company_list = response.json()["list"]
for dic in all_company_list:
_id = dic["ID"]
# print(_id)
#将id作为请求企业详情数据url的请求参数
detail_url = "http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsById"
data = {
"id":_id
}
response = requests.post(url=detail_url,headers=headers,data=data)
company_detail_dic = response.json()
person_name = company_detail_dic["businessPerson"]
addr = company_detail_dic["epsProductAddress"]
print(person_name,addr)
二、cookie
-
cookie是存储在客户端的一组键值对
-
cookie是由服务器端创建
-
cookie应用的简单示例:
- 免密登录(指定时长之内)
-
在爬虫中处理cookie的两种方式
- 手动处理
- 将cookie封装到headers字典中,将该字典作用到get/post方法的headers参数中
- 自动处理
- Session对象。
- Session对象的创建:requests.Session()
- 对象的作用:
- 可以跟requests一样调用get/post进行请求的发送。在使用session进行请求发送的过程中,如果产生了cookie,则cookie会被自动存储到session对象中。
- 在爬虫中使用session处理cookie时,session对象至少需要被用几次?
- 两次。第一次是为了捕获和存储cookie到session对象中,第二次就是用携带cookie的session进行请求发送,这次请求发送就是携带cookie发起的请求。
- 手动处理
import requests
sess = requests.Session()
headers = {
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36"
}
#访问首页生成cookie
sess.get(url="https://xueqiu.com/",headers=headers)
url = "https://xueqiu.com/statuses/hot/listV2.json?since_id=-1&max_id=319462&size=15"
#第二次访问自动携带cookie
json_data = sess.get(url=url,headers=headers).json()
print(json_data)
三、数据解析
数据解析使用的源代码如下
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>测试bs4</title>
</head>
<body>
<div>
<p>百里守约</p>
</div>
<div class="song">
<p>李清照</p>
<p>王安石</p>
<p>苏轼</p>
<p>柳宗元</p>
<a href="http://www.song.com/" title="赵匡胤" target="_self">
<span>this is span</span>
宋朝是最强大的王朝,不是军队的强大,而是经济很强大,国民都很有钱</a>
<a href="" class="du">总为浮云能蔽日,长安不见使人愁</a>
<img src="http://www.baidu.com/meinv.jpg" alt="" />
</div>
<div class="tang">
<ul>
<li><a href="http://www.baidu.com" title="qing">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村</a></li>
<li><a href="http://www.163.com" title="qin">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山</a></li>
<li><a href="http://www.126.com" alt="qi">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君</a></li>
<li><a href="http://www.sina.com" class="du">杜甫</a></li>
<li><a href="http://www.dudu.com" class="du">杜牧</a></li>
<li><b>杜小月</b></li>
<li><i>度蜜月</i></li>
<li><a href="http://www.haha.com" id="feng">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘</a></li>
</ul>
</div>
</body>
</html>
3.1 站长素材图片数据的爬取
- 反爬机制:图片懒加载。只有当图片数据被显示在可视化范围之内,则图片才会被加载出来。
- 伪属性:src2,阻止图片加载的。只有当伪属性被变成真正的src属性值图片才会被加载出来。
- 分析:
- 图片数据是否为动态加载的数据
- 除了可以在response选项卡中进行局部搜索外,我们该可以观察preview这个选项卡中的可视化内容
- 发现preview中只显示了图片的名称,并没有显示图片数据。
- 图片数据是否为动态加载的数据
import requests
import re
headers = {
"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36"
}
url = "http://sc.chinaz.com/tag_tupian/YaZhouMeiNv.html"
page_text = requests.get(url,headers=headers).text #获取字符串形式的响应数据
#通过正则进行图片地址的解析
ex = "<a.*?<img src2="(.*?)" alt.*?</a>"
img_src_list = re.findall(ex,page_text,re.S)#re.S处理回车
3.2 bs4解析
-
bs4数据解析的解析原理/流程
- 实例化一个BeautifulSoup的对象,且将等待被解析的数据加载到该对象中
- 方式1:
- BeautifulSoup(fp,”lxml”):解析本地存储的html文件
- 方式2:
- BeautifulSoup(page_text,”lxml”):解析互联网上请求到的页面数据)
- 方式1:
- 调用BeautifulSoup对象中的相关方法和属性进行标签定位和数据的提取
- 实例化一个BeautifulSoup的对象,且将等待被解析的数据加载到该对象中
-
环境的安装:
- pip install bs4
- pip install lxml
-
标签定位
- soup.tagName: 返回第一次出现的tagName标签
- 属性定位:soup.find(“tagName”,attrName=”value”)
- findAll和find的用法一样,但是返回值不一样
- 选择器定位:
- select(“selector”)
-
数据的提取
- 提取标签中存在的数据
- .string:取出标签直系的文本内容
- .text:取出标签中所有的文本内容
- 提取标签属性中存储的数据
- tagName[“attrName”]
- 提取标签中存在的数据
from bs4 import BeautifulSoup
#bs4中有哪些方法和属性可以被我们使用
fp = open("./test.html","r")
soup = BeautifulSoup(fp,"lxml")
print(soup) #对象打印的结果就是加载到该对象中被解析的数据
print(soup.div) #获取div标签的数据
#------获取结果为:
<div>
<p>百里守约</p>
</div>
#------
#属性定位:根据属性定位具体的标签
soup.find("div",class_="song")#class属性为song的div标签
soup.find("a",id="feng")
soup.select("#feng")#根据id选择器定位a标签
soup.select(".song")#定位class为song的标签
#层级选择器
soup.select(".tang > ul > li > a") # >表示一个层级
soup.select(".tang a") #空格表示多个层级
#更多
soup.p.string
oup.div.text
soup.a["href"]
示列:
#使用bs4解析爬取三国演义整片小说内容http://www.shicimingju.com/book/sanguoyanyi.html
#从首页解析出章节的标题和详情页的url
url = "http://www.shicimingju.com/book/sanguoyanyi.html"
headers = {
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36"
}
page_text = requests.get(url,headers=headers).text #首页的页面源码数据
fp = open("./sanguo.txt","a+",encoding="utf-8")
#数据解析(章节标题,详情页的url)
soup = BeautifulSoup(page_text,"lxml")
#定位到了所有的标题对应的a标签
a_list = soup.select(".book-mulu > ul > li > a")
for a in a_list:
title = a.string
detail_url = "http://www.shicimingju.com"+a["href"]
#解析提取章节内容
page_text_detail = requests.get(url=detail_url,headers=headers).text
#解析详情页中的章节内容
soup = BeautifulSoup(page_text_detail,"lxml")
content = soup.find("div",class_="chapter_content").text
fp.write(title+":"+content+"
")
print(title,"下载成功!")
3.3 xpath
(1) xpath解析相关
- html标签结构
- 是一个树状的结构
- xpath解析原理
- 实例化一个etree对象,且将即将被解析的数据加载到该对象中
- 解析本地存储的html文档:
- etree.parse(“fileName”)
- 解析网上爬取的html数据:
- etree.HTML(page_text)
- 解析本地存储的html文档:
- 使用etree对象中的xpath方法结合着不同的xpath表达式实现标签定位和数据提取
- 实例化一个etree对象,且将即将被解析的数据加载到该对象中
(2) xpath表达式
- 标签定位
- 最左侧的/:必须要从根标签开始逐层的定位目标标签
- 非最最侧的/:表示一个层级
- 最左侧的//:可以从任意位置定义目标标签
- 非最左侧的//:表示多个层级
- 属性定位://tagName[@attrName=”value”]
- 索引定位://tagName[index],index索引是从1开始
- 模糊匹配:
- //div[contains(@class, “ng”)] 定位到class属性值中包含ng的div标签
- //div[starts-with(@class, “ta”)] 定位到class属性值中是以ta开头的div标签
- 数据提取
- 取标签中的数据
- /text():直系文本内容
- //text():所有的文本内容
- 去属性的数据
- tagName/@attrName
- 取标签中的数据
from lxml import etree
tree = etree.parse("./test.html")#将本地存储的html文档进行解析
tree.xpath("/html/head")#从根标签开始定位head标签
tree.xpath("//head") #将html文档中所有的head标签定位到
#定位class为song的div标签
tree.xpath("//div[@class="song"]")
tree.xpath("//li[1]")
#找到id为feng的a标签的文本内容
tree.xpath("//a[@id="feng"]/text()")
#获取class为song的div标签的文本内容
tree.xpath("//div[@class="song"]//text()")
#获取id为feng的a标签的href值
tree.xpath("//a[@id="feng"]/@href")
批量下载图片示列:
import os
#爬取图片数据和图片名称将其保存到本地
dirName = "imgLibs"
if not os.path.exists(dirName):
os.mkdir(dirName)
#第一页:http://pic.netbian.com/4kmeinv/
#非第一页:http://pic.netbian.com/4kmeinv/index_2.html
url = "http://pic.netbian.com/4kmeinv/index_%d.html"
headers = {
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36"
}
for page in range(1,6):
if page == 1:
new_url = "http://pic.netbian.com/4kmeinv/"
else:
new_url = format(url%page)#表示非第一页的url
response = requests.get(new_url,headers=headers)
response.encoding = "gbk"
page_text = response.text
#数据解析:图片地址和图片名称
tree = etree.HTML(page_text)
#定位到了所有的li标签
li_list = tree.xpath("//div[@class="slist"]/ul/li")#全局数据解析
for li in li_list:
img_src = "http://pic.netbian.com"+li.xpath("./a/img/@src")[0]#局部的数据解析, ./表示的就是xpath调用者对应的标签
img_name = li.xpath("./a/img/@alt")[0]+".jpg"
img_data = requests.get(img_src,headers=headers).content
filePath = dirName+"/"+img_name
with open(filePath,"wb") as fp:
fp.write(img_data)
print(img_name,"下载成功!!!")
xpath小扩展:
#如何提升xpath表达式的通用性
url = "https://www.aqistudy.cn/historydata/"
page_text = requests.get(url,headers=headers).text
tree = etree.HTML(page_text)
hot_cities = tree.xpath("//div[@class="bottom"]/ul/li/a/text()")
all_cities = tree.xpath("//div[@class="bottom"]/ul/div[2]/li/a/text()")
#上述的两个xpath表达式是否可以合并成一个xpath表达式
tree.xpath("//div[@class="bottom"]/ul/li/a/text() | //div[@class="bottom"]/ul/div[2]/li/a/text()")
四、代理
- 代理和爬虫之间的关联?
- 爬虫程序可能会在短时间内对指定的服务器发起高频的请求。服务器端会将该高频请求的ip禁掉。
- 代理的匿名度
- 透明:对方服务器知道你使用了代理也知道你的真实ip
- 匿名:知道你使用了代理,但是不知道你的真是ip
- 高匿:不知道你使用了代理,更不知道你的真是ip
- 代理的类型
- http:只能代理http协议的请求
- https:代理https协议的请求
- 如何获取代理服务器?
- 免费:几乎不能用
- 西祠代理
- 快代理
- goubanjia
- 付费:
- 代理精灵:http://http.zhiliandaili.cn/
- 免费:几乎不能用
from lxml import etree
import random
#1.构建一个代理池
ips_list = []
url = "http://t.11jsq.com/index.php/api/entry?method=proxyServer.generate_api_url&packid=1&fa=0&fetch_key=&groupid=0&qty=52&time=1&pro=&city=&port=1&format=html&ss=5&css=&dt=1&specialTxt=3&specialJson=&usertype=2"
headers = {
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36"
}
page_text = requests.get(url=url,headers=headers).text
tree = etree.HTML(page_text)
ip_list = tree.xpath("//body//text()")
for ip in ip_list:
dic = {"https":ip}
ips_list.append(dic)
ips_list
#使用代理池操作
url = "https://www.xicidaili.com/nn/%d"
all_data = []
for page in range(1,30):
new_url = format(url%page)
#proxies={"http":"ip:port"}
page_text = requests.get(url=new_url,headers=headers,proxies=random.choice(ips_list)).text
tree = etree.HTML(page_text)
#在xpath表达式中不可以出现tbody标签,否则会出问题
tr_list = tree.xpath("//*[@id="ip_list"]//tr")[1:]
for tr in tr_list:
ip_addr = tr.xpath("./td[2]/text()")[0]
all_data.append(ip_addr)
print(len(all_data))
五、验证码识别
-
线上的打码平台进行验证码识别
-
– 云打码:http://www.yundama.com/about.html
-
– 超级鹰(使用):http://www.chaojiying.com/about.html
-
– 打码兔
-
-
超级鹰
-
– 注册:身份【用户中心】
-
– 登录:身份【用户中心】
-
– 创建一个软件:软件ID-》生成一个软件ID(899370)
-
– 下载示例代码:开发文档-》python
-
#!/usr/bin/env python
# coding:utf-8
import requests
from hashlib import md5
class Chaojiying_Client(object):
def __init__(self, username, password, soft_id):
self.username = username
password = password.encode("utf8")
self.password = md5(password).hexdigest()
self.soft_id = soft_id
self.base_params = {
"user": self.username,
"pass2": self.password,
"softid": self.soft_id,
}
self.headers = {
"Connection": "Keep-Alive",
"User-Agent": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)",
}
def PostPic(self, im, codetype):
"""
im: 图片字节
codetype: 题目类型 参考 http://www.chaojiying.com/price.html
"""
params = {
"codetype": codetype,
}
params.update(self.base_params)
files = {"userfile": ("ccc.jpg", im)}
r = requests.post("http://upload.chaojiying.net/Upload/Processing.php", data=params, files=files, headers=self.headers)
return r.json()
def ReportError(self, im_id):
"""
im_id:报错题目的图片ID
"""
params = {
"id": im_id,
}
params.update(self.base_params)
r = requests.post("http://upload.chaojiying.net/Upload/ReportError.php", data=params, headers=self.headers)
return r.json()
if __name__ == "__main__":
chaojiying = Chaojiying_Client("超级鹰用户名", "超级鹰用户名的密码", "96001") #用户中心>>软件ID 生成一个替换 96001
im = open("a.jpg", "rb").read() #本地图片文件路径 来替换 a.jpg 有时WIN系统须要//
print chaojiying.PostPic(im, 1902) #1902 验证码类型 官方网站>>价格体系 3.4+版 print 后要加()
#调用识别验证码的函数对验证码进行识别
transform_code_img("./a.jpg",4004)
示列:古诗文模拟登陆
from lxml import etree
#1.解析出本次登录页面对应的验证码图片地址
login_url = "https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx"
headers = {
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36"
}
page_text = requests.get(url=login_url,headers=headers).text
tree = etree.HTML(page_text)
#解析出了验证码图片的地址
img_path = "https://so.gushiwen.org"+tree.xpath("//*[@id="imgCode"]/@src")[0]
img_data = requests.get(url=img_path,headers=headers).content #请求到了图片数据
#将图片保存到本地存储
with open("./code.jpg","wb") as fp:
fp.write(img_data)
#识别验证码
code_result = transform_code_img("./code.jpg",1004)
print(code_result)
六、模拟登录
-
模拟登录中涉及的反爬:
-
– 验证码
-
– 动态变化的请求参数,多次访问,看请求参数是否发生变化,发生变化的参数是否包含在源代码中.
-
– cookie
-
import requests
sess = requests.Session() #创建好session对象
#处理动态变化的请求参数
#1.解析出本次登录页面对应的验证码图片地址
login_url = "https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx"
headers = {
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36"
}
page_text = sess.get(url=login_url,headers=headers).text
tree = etree.HTML(page_text)
#解析出了验证码图片的地址
img_path = "https://so.gushiwen.org"+tree.xpath("//*[@id="imgCode"]/@src")[0]
img_data = sess.get(url=img_path,headers=headers).content #请求到了图片数据
#将图片保存到本地存储
with open("./code.jpg","wb") as fp:
fp.write(img_data)
#将动态变化的请求参数从页面源码中解析出来
__VIEWSTATE = tree.xpath("//*[@id="__VIEWSTATE"]/@value")[0]
__VIEWSTATEGENERATOR = tree.xpath("//*[@id="__VIEWSTATEGENERATOR"]/@value")[0]
#识别验证码
code_result = transform_code_img("./code.jpg",1004)
print(code_result)
post_url = "https://so.gushiwen.org/user/login.aspx?from=http%3a%2f%2fso.gushiwen.org%2fuser%2fcollect.aspx"
data = {
"__VIEWSTATE":__VIEWSTATE,
"__VIEWSTATEGENERATOR":__VIEWSTATEGENERATOR,
"from": "http://so.gushiwen.org/user/collect.aspx",
"email": "www.zhangbowudi@qq.com",
"pwd": "bobo328410948",
"code": code_result,
"denglu": "登录",
}
#模拟登录的请求
response = sess.post(url=post_url,headers=headers,data=data)
page_text = response.text #登录成功后页面的源码数据
with open("gushiwen.html","w",encoding="utf-8") as fp:
fp.write(page_text)
七、线程池
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import time
from multiprocessing.dummy import Pool
import requests
#同步代码
# urls = [
# "http://127.0.0.1:5000/bobo",
# "http://127.0.0.1:5000/jay",
# "http://127.0.0.1:5000/tom"
# ]
# def get_request(url):
# page_text = requests.get(url).text
# print(len(page_text))
#
# if __name__ == "__main__":
# start = time.time()
# for url in urls:
# get_request(url)
# print("总耗时:",time.time()-start)
#基于线程池的异步效果
urls = [
"http://127.0.0.1:5000/bobo",
"http://127.0.0.1:5000/jay",
"http://127.0.0.1:5000/tom"
]
def get_request(url):
page_text = requests.get(url).text
return len(page_text)
if __name__ == "__main__":
start = time.time()
pool = Pool(3) #启动了三个线程
#参数1:回调函数
#参数2:可迭代的对象,alist
#作用:可以将alist中的每一个元素依次传递给回调函数作为参数,然后回调函数会异步
#对列表中的元素进行相关操作运算
#map的返回值就是回调函数返回的所有结果
page_text_len_list = pool.map(get_request,urls)
print(page_text_len_list)
print("总耗时:",time.time()-start)