爬虫之requests模块的使用
requests模块
- 概念:基于网络请求的模块
- 作用:用来模拟浏览器发请求,从而实现爬虫
- 环境安装:pip install requests
- 编码流程:
- 指定url
- 发起请求
- 获取响应数据
- 持久化存储
示例:
1:爬取搜狗首页的页面源码数据
import requests
#1.指定url
url = 'https://www.sogou.com/'
#2.请求发送:get返回的是一个响应对象
response = requests.get(url=url)
#3.获取响应数据:text返回的是字符串形式的响应数据
page_text = response.text
#4.持久化存储
with open('sogou.html','w',encoding='utf-8') as fp:
fp.write(page_text)
2:实现一个简易的网页采集器(请求参数的动态化)
url = 'https://www.sogou.com/web'
query = input('请输入参数:')
params = {
'query': query
}
response = requests.get(url=url, params=params)
file_name = query + '.html'
page_text = response.text
with open(file_name, 'w', encoding='utf-8') as fp:
fp.write(page_text)
上述代码问题:
- 乱码问题
- response.encoding = 'xxx'
- 数据丢失
- 反爬机制: UA(User-Agent)检测
- 反反爬策略: UA伪装
# 改进上述代码
url = 'https://www.sogou.com/web'
query = input('请输入参数:')
params = {
'query': query
}
# UA伪装
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36'
}
response = requests.get(url=url, params=params, headers=headers)
# 指定编码格式
response.encoding = 'utf-8'
file_name = query + '.html'
page_text = response.text
with open(file_name, 'w', encoding='utf-8') as fp:
fp.write(page_text)
3:动态加载的数据
-
通过另一个网络请求(例如ajax)请求到的数据
-
爬取豆瓣电影中动态加载出的电影详情数据
url = 'https://movie.douban.com/j/chart/top_list'
params = {
'type': '5',
'interval_id': '100:90',
'action': '',
'start': '0',
'limit': '10'
}
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36'
}
response = requests.get(url=url, params=params, headers=headers)
# 在已知响应数据是json字符串时,可以使用json()直接获得反序列化的原数据
movie_list = response.json()
for movie in movie_list:
print(movie['title'], movie['score'])
总结: 对一个陌生的网站进行数据爬取的时候,首先要确定的一点就是爬取的数据是否为动态加载出来的
- 是: 需要通过抓包工具捕获到动态加载数据对应的数据包,从中提取出url和请求参数
- 不是: 直接对浏览器地址栏的url发起请求即可
如何检测爬取的数据是不是动态加载出来的?
- 通过抓包工具进行局部搜索(response)就可以验证数据是否为动态加载
- 搜索到: 不是动态加载
- 搜索不到: 是动态加载
如何定位动态加载的数据在哪呢?
- 通过抓包工具进行全局搜索进行定位
4:爬取肯德基餐厅位置信息
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/77.0.3865.90 Safari/537.36'
}
data = {
'cname': '',
'pid': '',
'keyword': '北京',
'pageIndex': '1',
'pageSize': '10'
}
response = requests.post(url=url, data=data, headers=headers)
address_dic = response.json()
for address in address_dic['Table1']:
print(address['cityName'], address['addressDetail'])
5: 需求 https://www.fjggfw.gov.cn/Website/JYXXNew.aspx 福建省公共资源交易中心,提取完整的html中标信息
实现思路
- 确认爬取的数据都是动态加载出来的
- 在首页中捕获到ajax请求对应的数据包,从该数据包中提取出请求的url和请求参数
- 对提取到的url进行请求发送,获取响应数据(json)
- 从json串中提取到每一个公告对应的id值
- 将id值和中标信息对应的url进行整合,进行请求发送捕获到每一个公告对应的中标信息数据
post_url = 'https://www.fjggfw.gov.cn/Website/AjaxHandler/BuilderHandler.ashx'
# 此处用到了cookie
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36',
'Cookie': '_qddac=4-3-1.4euvh3.dolhcp.k1hv0g18; ASP.NET_SessionId=zlodpss0z5marc42xbwf3k1z; Hm_lvt_94bfa5b89a33cebfead2f88d38657023=1570540077; __root_domain_v=.fjggfw.gov.cn; _qddaz=QD.xezyl8.p60887.k1hv0fxc; _qdda=4-1.4euvh3; _qddab=4-dolhcp.k1hv0g18; _qddamta_2852155767=4-0; Hm_lpvt_94bfa5b89a33cebfead2f88d38657023=1570540248; _qddagsx_02095bad0b=01ea1a6c5d3a64853ca5827a992c7e8755a27bdf6483c4170cf2f7408bf5160b8be84faf079220f53eb77ffddb8e7a31bb676d8e2335aa55f11f4fd4ea8e3ae123c0a5f18a8ab6b832b0d1b4888af4bdd0787e3a2fbda9234cb86cd2b05adf3e56d7e29aafcb05c7edc7e73de6cb346d19449446dc77234a6fb176cd0c0e4df4'
}
for n in range(1, 6):
data = {
'OPtype': 'GetListNew',
'pageNo': n,
'pageSize': '10',
'proArea': '-1',
'category': 'GCJS',
'announcementType': '-1',
'ProType': '-1',
'xmlx': '-1',
'projectName': '',
'TopTime': '2019-07-10 00:00:00',
'EndTime': '2019-10-08 23:59:59',
'rrr': '0.5270491290780797'
}
post_data = requests.post(url=post_url, data=data, headers=headers).json()
for i in post_data['data']:
mid = int(i['M_ID'])
url = f'https://www.fjggfw.gov.cn/Website/AjaxHandler/BuilderHandler.ashx?OPtype=GetGGInfoPC&ID={mid}&GGTYPE=5&url=AjaxHandler%2FBuilderHandler.ashx'
response = requests.get(url=url, headers=headers)
response.encoding = 'utf-8'
data_dic = response.json()
with open('作业.text', 'a', encoding='utf-8') as f1:
f1.write(''.join(data_dic['data']))
f1.write('\n----------------------------')
6: 爬取图片
- 基于requests
- 基于urllib
- 区别: urllib中的urlretrieve不可以进行UA伪装
# 基于requests模块的图片爬取
import requests
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'
}
url = 'http://tva1.sinaimg.cn/mw600/007QUzsKgy1g7qzr59hk7j30cs0gxn82.jpg'
# content返回的是bytes类型的响应数据
img_data = requests.get(url, headers=headers).content
with open('ceshi.jpg', 'wb') as f1:
f1.write(img_data)
# 基于urllib的图片爬取
from urllib import request
url = 'http://tva1.sinaimg.cn/mw600/007QUzsKgy1g7qzr59hk7j30cs0gxn82.jpg'
request.urlretrieve(url, 'ceshi2.jpg')
7: 反爬之图片懒加载
需求: 爬取http://sc.chinaz.com/tag_tupian/YaZhouMeiNv.html 网站的图片
import requests
import os
from lxml import etree
# http://sc.chinaz.com/tag_tupian/YaZhouMeiNv.html 网站中前5页的图片数据进行爬取和持久化存储
file_path = './作业/'
if not os.path.exists(file_path):
os.mkdir(file_path)
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36'
}
url = 'http://sc.chinaz.com/tag_tupian/yazhoumeinv_%d.html'
for page in range(1, 6):
if page == 1:
new_url = 'http://sc.chinaz.com/tag_tupian/YaZhouMeiNv.html'
else:
new_url = url % page
page_text = requests.get(new_url, headers=headers).text
tree = etree.HTML(page_text)
div_list = tree.xpath('//div[@id="container"]/div')
for div in div_list:
title = div.xpath('./p/a/text()')[0].encode('iso-8859-1').decode('utf-8')
img_path = file_path + '/' + title + '.jpg'
img_url = div.xpath('./div/a/img/@src2')[0]
img_data = requests.get(img_url, headers=headers).content
with open(img_path, 'wb') as f1:
f1.write(img_data)
print('第{}页爬取完毕~~'.format(page))
反爬机制之: 图片懒加载
- 使用伪属性记录图片地址
- 当图片进入浏览器可视窗口时,才通过JS使其加载出来(将伪属性修改为src
对应的反反爬策略:
- 爬取伪属性所对应的属性
8: 爬取梨视频(JS动态加载视频地址)
import requests
import os
import re
from lxml import etree
# 梨视频短视频的爬取
file_path = './作业3/'
if not os.path.exists(file_path):
os.mkdir(file_path)
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36'
}
url = 'https://www.pearvideo.com/category_59'
page_text = requests.get(url, headers=headers).text
tree = etree.HTML(page_text)
li_list = tree.xpath('//li[@class="categoryem "]')
for li in li_list:
title = li.xpath('./div/a/div[2]/text()')[0]
movie_url = 'https://www.pearvideo.com/' + li.xpath('./div/a/@href')[0]
movie_text = requests.get(movie_url, headers=headers).text
src = re.findall('srcUrl="(.*?)",vdoUrl', movie_text, re.S)[0]
movie_data = requests.get(src, headers=headers).content
movie_path = file_path + title + '.' + src.split('.')[-1]
with open(movie_path, 'wb') as f1:
f1.write(movie_data)
print('好了一个~')
print('全部爬取完毕~')
反爬机制之:
- JS动态加载视频地址
对应的反反爬策略:
- 使用正则匹配到JS代码中的视频地址
9: 反爬之IP检测(代理)
出现HttpConnectionPool(host:XX) Max retries exceeded with url错误
- 产生原因:
- 1.短时间内对服务器端发起了高频请求
- 处理: headers中加入Connection: 'close'
- 2.请求对应的ip被服务器端禁止
- 使用代理
反爬机制之:
- IP异常检测
对应的反反爬策略:
- 使用代理
代理操作
- 概念: 代理服务器
代理的作用?
- 请求和响应的转发(拦截请求和响应)
代理和爬虫之间的关联是什么?
- 可以基于代理实现更换爬虫程序请求的ip地址
代理ip的网站
- 西刺
- 快代理
- www.goubanjia.com
- 代理精灵: http://http.zhiliandaili.cn/
代理的匿名度
- 高匿
- 匿名
- 透明
类型
- http
- https
示例1:
# 使用代理的示例
import requests
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36',
'Connection': 'close'
}
url = 'https://www.baidu.com/s?ie=UTF-8&wd=ip'
page_text = requests.get(url, headers=headers, proxies={'https': '182.85.41.159:41729'}).text
with open('./代理测试.html', 'w', encoding='utf-8') as f1:
f1.write(page_text)
示例2:
# 搭建一个免费的代理池
# 爬取西刺代理
url = 'https://www.xicidaili.com/nn/{}'
ip_list = []
for page in range(1, 51):
new_url = url.format(page)
page_text = requests.get(new_url, headers=headers).text
tree = etree.HTML(page_text)
# 注意,xpath中不能出现tbody标签
tr_list = tree.xpath('//table//tr')[1:]
for tr in tr_list:
dic = {}
dic['ip_port'] = tr.xpath('./td[2]/text()')
dic['agreement'] = tr.xpath('./td[6]/text()')
ip_list.append(dic)
print(len(ip_list))
# 多次爬取后发现,ip被服务器端封禁
# 使用代理进行反反爬
示例3:
# 构建一个付费的代理池
import random
url = 'http://ip.11jsq.com/index.php/api/entry?method=proxyServer.generate_api_url&packid=2&fa=0&fetch_key=&groupid=0&qty=50&time=1&pro=&city=&port=1&format=html&ss=5&css=&dt=1&specialTxt=3&specialJson=&usertype=15'
page_text = requests.get(url, headers=headers).text
tree = etree.HTML(page_text)
ip_list = tree.xpath('//body//text()')
ips_pool = []
for ip in ip_list:
ips_pool.append({'https': ip})
url = 'https://www.xicidaili.com/nn/{}'
ip_list = []
for page in range(1, 51):
new_url = url.format(page)
try:
page_text = requests.get(new_url, headers=headers, proxies=random.choice(ips_pool)).text
tree = etree.HTML(page_text)
# 注意,xpath表达式中不能出现tbody标签
tr_list = tree.xpath('//table//tr')[1:]
for tr in tr_list:
dic = {}
dic['ip_port'] = tr.xpath('./td[2]/text()')
dic['agreement'] = tr.xpath('./td[6]/text()')
ip_list.append(dic)
except Exception:
pass
print(len(ip_list))
10: 反爬之cookie
需求:
- 爬取www.xueqiu.com 中的新闻数据
爬虫中处理cookie的操作
- 手动处理: 将cookie写在headers中
- 自动处理: session对象
- 获取session对象: requests.Session()
- 作用:
- session对象和requests对象都可以对指定的url进行请求发送,只不过使用session对象进行请求发送如果产生了cookie,则cookie会被自动保存在session对象中.
url = 'https://xueqiu.com/v4/statuses/public_timeline_by_category.json?since_id=-1&max_id=20352414&count=15&category=-1'
page_text = requests.get(url, headers=headers).json()
# 'error_description': '遇到错误,请刷新页面或者重新登录帐号后再试',
# 基于cookie操作的修正
session = requests.Session()
cookie_url = 'https://xueqiu.com'
# 首先使用session对象向https://xueqiu.com发送一次请求,记录产生的cookie
session.get(cookie_url, headers=headers)
url = 'https://xueqiu.com/v4/statuses/public_timeline_by_category.json?since_id=-1&max_id=20352414&count=15&category=-1'
# 保证该次请求携带对应的cookie才可以请求成功
page_text = session.get(url, headers=headers).json()
print(page_text)
11: 模拟登陆&验证码的识别&动态请求参数
使用线上的打码平台进行自动的识别验证码
- 云打码
- 超级鹰
- 注册,登录
- 创建一个软件
- 下载示例代码(开发文档中)
开发文档:
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()
获取验证码:
# 获取验证码
def get_code(img_path, img_type):
chaojiying = Chaojiying_Client('账号', '密码', '软件id')
im = open(img_path, 'rb').read()
return chaojiying.PostPic(im, img_type)['pic_str']
爬取代码:
from lxml import etree
login_url = 'https://so.gushiwen.org/user/login.aspx'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36',
'Connection': 'close'
}
session = requests.Session()
login_text = session.get(login_url, headers=headers).text
tree = etree.HTML(login_text)
# 验证码的识别: 将验证码下载到本地然后提交给打码平台进行识别
# 事后发现: cookie是在获取验证码图片时产生的
code_img_url = 'https://so.gushiwen.org' + tree.xpath('//*[@id="imgCode"]/@src')[0]
code_img_data = session.get(code_img_url, headers=headers).content
with open('./验证码.jpg', 'wb') as f1:
f1.write(code_img_data)
code = get_code('./验证码.jpg', 1004)
# 获取动态的请求参数
__VIEWSTATE = tree.xpath('//*[@id="__VIEWSTATE"]/@value')[0]
__VIEWSTATEGENERATOR = tree.xpath('//*[@id="__VIEWSTATEGENERATOR"]/@value')[0]
data = {
'__VIEWSTATE': __VIEWSTATE,
'__VIEWSTATEGENERATOR': __VIEWSTATEGENERATOR,
'from': 'http://so.gushiwen.org/user/collect.aspx',
'email': '账号',
'pwd': '密码',
'code': code,
'denglu': '登录'
}
page_text = session.post(login_url, data=data, headers=headers).text
with open('./测试.html', 'w', encoding='utf-8') as f1:
f1.write(page_text)
如何捕获动态变化的请求参数
- 通常情况下,动态变化的请求参数都会被隐藏在前台页面源码数据中
目前接触到的反爬机制及反反爬策略:
- robots: 不管它
- UA检测: UA伪装
- 图片懒加载: 使用伪属性获取图片地址
- IP检测(代理): 使用代理
- cookie: 使用session对象
- 验证码: 使用线上打码平台解析验证码
- 动态变化的请求参数: 从页面源码中解析动态变化的请求参数
- 动态加载的数据: 使用抓包工具,全局搜索,定位到动态加载的数据的位置
12: 使用线程池提升爬取数据的效率
同步操作的代码:
import time
# 同步操作的代码
def request(url):
print('正在请求:', url)
time.sleep(2)
print('请求完毕:', url)
urls = [
'www.1.com',
'www.2.com',
'www.3.com'
]
start = time.time()
for url in urls:
request(url)
print('总耗时:', time.time() - start)
# 6秒
基于线程池的异步操作代码:
import time
from multiprocessing.dummy import Pool # 线程池
# 基于线程池的异步操作代码
pool = Pool(3)
def request(url):
print('正在请求:', url)
time.sleep(2)
print('请求完毕:', url)
urls = [
'www.1.com',
'www.2.com',
'www.3.com'
]
start = time.time()
pool.map(request, urls)
print('总耗时:', time.time() - start)
# 2秒
爬虫加线程池:
# 爬虫加线程池
import time
import requests
from multiprocessing.dummy import Pool # 线程池
from lxml import etree
urls = [
'http://127.0.0.1:5000/hxbs',
'http://127.0.0.1:5000/index'
]
# 发送请求
def get_request(url):
page_text = requests.get(url).text
return page_text
# 解析数据
def parse(page_text):
tree = etree.HTML(page_text)
print(tree.xpath('//div[1]//text()'))
pool = Pool(2)
start = time.time()
page_text_list = pool.map(get_request, urls)
print(len(page_text_list))
pool.map(parse, page_text_list)
print('总耗时:', time.time() - start)