爬虫-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:
- 请求参数:
- id: 536878abac734332ae06dcb1a3fbd14a
- id: 950d66fbf8714fbc9e799010e483d2d5
- 结论:每一家企业详情数据对应的请求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+'\n')
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
- 付费:
- 免费:几乎不能用
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.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)
浙公网安备 33010602011771号