第三章.requests 模块
3.1基本概念
-
什么是requests模块?
-
一种基于网络请求的模块,作用就是用来模拟浏览器发起请求
-
-
为什么要使用requests模块?
-
因为在使用urllib模块的时候,会有诸多不便之处,总结如下
-
手动处理url编码
-
手动处理post请求参数
-
处理cookie和代理操作繁琐.......
-
-
-
如何使用requests模块
-
安装:
-
pip install requests
-
-
使用流程
-
指定url
-
基于requests模块发起请求
-
获取响应对象中的数据值
-
持久化存储
-
-
-
什么是动态加载的数据?
-
由另一个额外的请求请求到的数据
-
-
如何判断一个页面中的是否存在动态加载的数据?
-
抓包工具进行局部搜索
-
-
如果判定出页面中有动态加载的数据,如何进行数据的定位?
-
使用抓包工具进行全局搜索
-
-
对一个陌生的网站数据进行爬取前一定要判定你爬取到的数据是否为动态加载的!!!
3.2代码展示
-
需求一 :爬取xx首页对应的源码数据
import requests
#1.指定地址
url="https://www.sogou.com"
#返回值是一个响应对象
response = requests.get(url=url)
#text返回的是字符串形式的相应数据
page_text = response.text
#持久化存储
with open("./sougou.html","w",encoding="utf-8") as f:
f.write(page_text) -
需求二 :基于xx编写一个简单的页面采集器
import requests
#想要将url携带的参数设定为动态参数
wd = input("enter a key:")
#url中?可加可不加
url = "https://www.sogou.com/web?"
#存储的就是动态请求参数
params = {
"query":wd
}
#一定需要将params作用到请求中
#params参数是对请求url参数的封装
response = requests.get(url=url,params=params)
#text返回的是字符串形式的相应数据
page_text = response.text
filename = wd + ".html"
#持久化存储
with open(filename,"w",encoding="utf-8") as f1:
f1.write(page_text)
print(wd,'下载成功')#上述程序出现问题:
问题一:出现了中文乱码
问题二:遇到了UA检测反爬机制
#请求载体身份标识的伪装:
User-Agent:请求载体身份标识,通过浏览器发起的请求,请求载体为浏览器,则该请求的User-Agent为浏览器的身份标识,使用爬虫程序发起的请求,则该请求的载体为爬虫程序,则该请求的User-Agent为爬虫程序的身份标识。可以通过判断该值来获知该请求的载体究竟是基于哪款浏览器还是基于爬虫程序。
#反爬机制
某些门户网站会对访问该网站的请求中的User-Agent进行捕获和判断,如果该请求的UA为爬虫程序,则拒绝向该请求提供数据。
#反反爬策略
将爬虫程序的UA伪装成某一款浏览器的身份标识。import requests
#想要将url携带的参数设定为动态参数
wd = input("enter a key:")
#url中?可加可不加
url = "https://www.sogou.com/web?"
#存储的就是动态请求参数
params = {
"query":wd
}
#即将发起请求对应的头信息
headers = {
"User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36"
}
#一定需要将params作用到请求中
#params参数是对请求url参数的封装
response = requests.get(url=url,params=params,headers=headers)
#手动修改相应数据的编码
response.encoding = "utf-8"
#text返回的是字符串形式的相应数据
page_text = response.text
filename = wd + ".html"
#持久化存储
with open(filename,"w",encoding="utf-8") as f1:
f1.write(page_text)
print(wd,'下载成功') -
需求三 : 爬取xx电影详情数据
#需求分析:
当滚轮滑动到底部的时候,发起一个ajax请求,且该请求请求到了一组电影数据
#动态加载的数据 :就是通过另外一个额外的请求到的数据
- ajax生成动态加载的数据
- js生成动态加载的数据import requests
url = "https://movie.douban.com/j/chart/top_list"
start = input("enter a start:")
limit = input("enter a limit:")
#处理请求参数
params = {
"type": "5",
'interval_id': '100:90',
'action': '',
'start': start,
'limit': limit
}
#即将发起请求对应的头信息
headers = {
"User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36"
}
response = requests.get(url=url,params=params,headers=headers)
#json返回的是序列化好的对象
data_list = response.json()
fp = open("douban.txt",'w',encoding="utf-8")
for dic in data_list:
name = dic["title"]
score = dic["score"]
fp.write(name + ":" + score + " ")
print(name,"爬取成功")
fp.close()
-
需求四 :爬取xx餐厅官网位置信息
链接地址 : http://www.kfc.com.cn/kfccda/storelist/index.aspx
import requests
post_url = "http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword"
city = input("enter a city:")
data = {
'cname': '',
'pid': '',
'keyword': city,
'pageIndex': '1',
'pageSize': '10',
}
headers = {
"User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36"
}
#data参数表示的就是get方法中的params
response = requests.post(url=post_url,data=data,headers=headers)
response.json() -
需求五 :爬取xx企业详情信息
链接地址 :http://125.35.6.84:81/xk/
#分析:
1.网站首页和企业的详情页都是动态加载的
2.分析某一家企业详情数据是怎麽来的
-企业详情是通过一个ajax请求(post)请求到的
#企业一:
-请求对应的:url:http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsById
该请求携带了一个参数
id=d39908bcf299469d8e2b1e35a9cd1eee
#企业二:
url:http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsById
id: 42410751abcf4cb39884774b0f1a97c5
-结论:
a.每家企业详情页都是通过一个post形式的ajax请求请求到的
b.每家企业对应ajax请求的url一样,请求方式是post,只有请求参数id不同
c.只需要获取每一家企业对应的id值即可获取每一家企业对应的详情数据
3.需要获取每家企业的id值
#思路:每家对应的id值应该存储在首页对应的相关请求或响应中
#结论:每一家企业的id值是存储在首页某一个ajax请求对应的数据中,只需要将该相应数据中的企业id一区/解析出即可基础版 ,存在问题:爬取速度慢,会出现无法爬取情况
import requests
import time
#要请求到每家企业对应的id
url = "http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsList"
#即将发起请求对应的头信息
headers = {
"User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36"
}
page = input("enter a page")
page = int(page)
for i in range(1,page+1):
i = str(i)
data = {
'on': 'true',
'page': i,
'pageSize': '15',
'productName': '',
'conditionType': '1',
'applyname': '',
'applysn': ''
}
#该json()的返回值中有每一家企业的id
data_dict = requests.post(url=url,data=data,headers=headers).json()
#解析id
for dic in data_dict["list"]:
_id = dic["ID"]
# print(_id)
#对每个id对应的企业详情数据进行捕获
post_url = "http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsById"
post_data = {
"id":_id
}
#该json()的返回值中有每一家企业的详细信息
detail_dic = requests.post(url=post_url,data=post_data,headers=headers).json()
company_name = detail_dic["epsName"]
address = detail_dic["epsProductAddress"]
fp = open("./company_detail.txt",'w',encoding="utf-8")
fp.write(company_name + ":" + address + " ")
time.sleep(0.5)
print(company_name,'爬取成功')
fp.close()代码优化版
import requests
import time
#要请求到每家企业对应的id
url = "http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsList"
#即将发起请求对应的头信息
headers = {
"User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36"
}
page = input("enter a page")
page = int(page)
with open("./company_detail.txt",'w',encoding="utf-8") as f:
for i in range(1,page+1):
i = str(i)
data = {
'on': 'true',
'page': i,
'pageSize': '15',
'productName': '',
'conditionType': '1',
'applyname': '',
'applysn': ''
}
#该json()的返回值中有每一家企业的id
data_dict = requests.post(url=url,data=data,headers=headers).json()
#解析id
for dic in data_dict["list"]:
_id = dic["ID"]
# print(_id)
#对每个id对应的企业详情数据进行捕获
post_url = "http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsById"
post_data = {
"id":_id
}
#该json()的返回值中有每一家企业的详细信息
detail_dic = requests.post(url=post_url,data=post_data,headers=headers).json()
company_name = detail_dic["epsName"]
address = detail_dic["epsProductAddress"]
f.write(company_name + ":" + address + " ")
print(company_name,'爬取成功') -
需求六:爬取西刺代理代理ip地址
import requests
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36'
}
#基于代理精灵构建一个ip池
from lxml import etree
all_ips = [] #列表形式的代理池
proxy_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'
proxy_page_text = requests.get(url=proxy_url,headers=headers).text
tree = etree.HTML(proxy_page_text)
proxy_list = tree.xpath('//body//text()')
for ip in proxy_list:
dic = {'https':ip}
all_ips.append(dic)
import random
#爬取西祠代理中的免费代理ip
url = 'https://www.xicidaili.com/nn/%d'
free_proxies = []
for page in range(1,30):
new_url = format(url%page)
page_text = requests.get(new_url,headers=headers,proxies=random.choice(all_ips)).text
tree = etree.HTML(page_text)
tr_list = tree.xpath('//*[@id="ip_list"]//tr')[1:]#xpath表达式中不可以出现tbody,第一个是列头
for tr in tr_list:
ip = tr.xpath('./td[2]/text()')[0]
port = tr.xpath('./td[3]/text()')[0]
t_type = tr.xpath('./td[7]/text()')[0]
dic = {
'ip':ip,
'port':port,
'type':t_type
}
free_proxies.append(dic)
print('第{}页爬取完毕!!!'.format(page))
print(len(free_proxies)) -
需求七 :爬取雪球网中的新闻咨询数据https://xueqiu.com
import requests
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36'
}
#获取一个session对象
session = requests.Session()
main_url = "https://xueqiu.com" #推测对该url发起请求会产生cookie
session.get(main_url,headers=headers)
url = "https://xueqiu.com/v4/statuses/public_timeline_by_category.json"
params = {
'since_id': '-1',
'max_id': '20347172',
'count': '15',
'category': '-1',
}
page_text = session.get(url=url,headers=headers,params=params).json()
page_text
3.3代理
3.3.1代理基本知识
-
代理
-
代理服务器 :实现请求转发,从而可以实现更换请求的ip地址
-
在requests中如何将请求的ip进行更换
-
-
代理的匿名度
-
透明: 服务器知道你使用了代理,并且知道了你的真实ip
-
匿名: 服务器知道你使用了代理,但不知道了你的真实ip
-
高匿: 服务器不知道你使用了代理,也不知道了你的真实ip
-
-
代理的类型
-
http :该类型的代理只可转发http协议的请求
-
https :该类型的代理只可转发https协议的请求
-
-
爬虫中为什么需要使用代理?
-
一些网站会有相应的反爬虫措施,例如很多网站会检测某一段时间某个IP的访问次数,如果访问频率太快以至于看起来不像正常访客,它可能就会会禁止这个IP的访问。所以我们需要设置一些代理IP,每隔一段时间换一个代理IP,就算IP被禁止,依然可以换个IP继续爬取。
-
-
代理的分类:
-
正向代理:代理客户端获取数据。正向代理是为了保护客户端防止被追究责任。
-
反向代理:代理服务器提供数据。反向代理是为了保护服务器或负责负载均衡。
-
-
免费代理ip提供网站
-
西祠代理
-
快代理
-
付费
-
代理精灵
-
快代理
-
-
在爬虫中遇到ip被禁用如何处理?
-
使用代理
-
构建一个代理
-
拨号服务器
-
-
更换本机ip
import requests
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36'
}
url = "https://www.baidu.com/s?wd=ip"
#proxies = {"http/https":"ip:port"}
page_text = requests.get(url=url,headers=headers,proxies={"https":"113.128.31.73:61234"}).text
with open("ip.html",'w',encoding="utf-8") as p:
p.write(page_text)
3.3.2设置ADSL拨号服务器代理
链接地址 :https://cuiqingcai.com/3443.html
3.3 cookie
-
作用 : 保存客户端相关状态
-
在请求中携带cookie,在爬虫遇到cookie的反爬如何处理?
1.手动处理
在抓包工具中捕获cookie,将其封装在headers中
应用场景:cookie无有效时长且不是动态变化
2.自动处理
使用session机制
应用场景:动态变化 -
session对象
-
该对象和requests模块用法几乎一致,如果在请求的过程中产生了cookie,如果该请求使用session发起的,则cookie会自动存储到session中
-
3.4 模拟登陆
验证码识别
-
相关的线上打码平台
-
打码兔
-
云打码
-
超级鹰 http://www.chaojiying.com/about.html
1.注册,登录(用户中心的身份认证)
2.登录后
创建一个软件: 点击 软件id -->生成一个软件id
下载一个示例代码: 开发文档-->python-->下载#平台示例代码的演示
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('547434796', 'yhh941218', '901271') #用户中心>>软件ID 生成一个替换 96001
im = open('a.jpg', 'rb').read() #本地图片文件路径 来替换 a.jpg 有时WIN系统须要//
print (chaojiying.PostPic(im, 1004)["pic_str"]) #1902 验证码类型 官方网站>>价格体系 3.4+版 print 后要加()
-
古诗文网的模拟登陆
https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx
-
将古诗文网中的验证码进行识别
import requests
from lxml import etree
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36'
}
url = "https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx"
page_text = requests.get(url=url,headers=headers).text
tree = etree.HTML(page_text)
img_src = "https://so.gushiwen.org" + tree.xpath('//*[@id="imgCode"]/@src')[0]
img_code_data = requests.get(img_src,headers=headers).content
with open('./gushiwen.jpg','wb') as f:
f.write(img_code_data)
def getCodeImgText(imgPath,img_type):
chaojiying = Chaojiying_Client('547434796', 'yhh941218', '901271') #用户中心>>软件ID 生成一个替换 96001
im = open(imgPath, 'rb').read() #本地图片文件路径 来替换 a.jpg 有时WIN系统须要//
return chaojiying.PostPic(im, img_type)["pic_str"] #1902 验证码类型 官方网站>>价格体系 3.4+版 print 后要加()
img_text = getCodeImgText("./gushiwen.jpg",1004)
print(img_text)
为什么在爬虫中要实现模拟登陆
#原因:
有的数据是必须经过登录后才显示出来的
-
需求 :对古诗文网做模拟登陆
import requests
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36'
}
def getCodeImgText(imgPath,img_type):
chaojiying = Chaojiying_Client('bobo328410948', 'bobo328410948', '899370') #用户中心>>软件ID 生成一个替换 96001
im = open(imgPath, 'rb').read() #本地图片文件路径 来替换 a.jpg 有时WIN系统须要//
return chaojiying.PostPic(im, img_type)['pic_str']
#使用session捕获cookie
s = requests.Session()
first_url = 'https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx'
s.get(first_url,headers=headers)
url = 'https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx'
page_text = requests.get(url,headers=headers).text
tree = etree.HTML(page_text)
img_src = 'https://so.gushiwen.org'+tree.xpath('//*[@id="imgCode"]/@src')[0]
img_code_data = s.get(img_src,headers=headers).content
with open('./gushiwen.jpg','wb') as fp:
fp.write(img_code_data)
img_text = getCodeImgText('./gushiwen.jpg',1004)
print(img_text)
#动态捕获动态的请求参数
__VIEWSTATE = tree.xpath('//*[@id="__VIEWSTATE"]/@value')[0]
__VIEWSTATEGENERATOR = tree.xpath('//*[@id="__VIEWSTATEGENERATOR"]/@value')[0]
#点击登录按钮后发起请求的url:通过抓包工具捕获
login_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': img_text,
'denglu': '登录',
}
main_page_text = s.post(login_url,headers=headers,data=data).text
with open('main.html','w',encoding='utf-8') as fp:
fp.write(main_page_text)#涉及到的反爬:
1.验证码
2.动态请求参数:每次请求对应的请求参数都是动态变化
3.cookie
#动态捕获:通常情况下,动态的请求参数都会被隐藏在前台页面的源码中
3.5 基于线程池的异步爬取
url = 'https://www.qiushibaike.com/text/page/%d/'
urls = []
for page in range(1,11):
new_url = format(url%page)
urls.append(new_url)
def get_request(url): #必须有一个参数
return requests.get(url,headers=headers).text
from multiprocessing.dummy import Pool
pool = Pool(10)
response_text_list = pool.map(get_request,urls) #使用自定义的函数func异步的处理urls列表中的每一个列表元素
print(response_text_list)