本篇介绍基于requests的爬虫。
概述
概念:基于网络请求的模块
作用:用来模拟浏览器发请求,从而实现爬虫
通用爬虫
步骤:
- 指定url
- 请求发送:get返回的是一个响应对象
- 获取响应数据: text返回的是字符串形式的响应数据
- 持久化存储
爬取搜狗首页的页面源码数据
1 | import requests |
实现一个简易的网页采集器
- 请求参数动态化(自定义字典给get方法的params传参)
- 使用UA伪装
1 | url = 'https://www.sogou.com/web' |
动态加载的数据
- 页面中想要爬取的内容并不是请求当前url得到的,而是通过另一个网络请求请求到的数据(例如,滚轮滑到底部,会发送ajax,对局部进行刷新)
例子:爬取豆瓣电影中动态加载出的电影详情
我们想爬取的页面是:豆瓣电影分类排行榜 - 科幻片 https://movie.douban.com/typerank?type_name=%E7%A7%91%E5%B9%BB&type=17&interval_id=100:90&action=
首先在chorme的抓包工具中进行全局搜索发现它不是请求当前url得到的,而是一个新的url:
https://movie.douban.com/j/chart/top_list?
当然这是一个ajax请求(在chorme的抓包工具中选择 XHR 可以进行查看)
在寻找另一部电影,请求的url仍然是
https://movie.douban.com/j/chart/top_list?
然后我们就需要找参数的规律了:
1
2
3
4
5
6
7
8
9
10
11type: 17
interval_id: 100:90
action:
start: 0
limit: 1
# 第二部
type: 17
interval_id: 100:90
action:
start: 20
limit: 20start 和 limit 是可以变化的,基于例二,我们自定义字典进行传参,然后进行尝试。
1 | url = 'https://movie.douban.com/j/chart/top_list' |
note:response.json() 可以免除我们手动使用json进行loads的过程。
爬取肯德基的餐厅位置信息
地址为:
http://www.kfc.com.cn/kfccda/storelist/index.aspx
post请求
一次爬取多页数据
思路:
- 首先,数据是动态加载的,分析请求方式为 post 发出的 url 与 data。
- 通过pageSize就可以在循环内完成多所有地址的爬取。
1 | url = 'http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword' |
补充:可以使用代理服务器,如搜索:全网代理IP
中标公告提取
- 需求
https://www.fjggfw.gov.cn/Website/JYXXNew.aspx 福建省公共资源交易中心
提取内容:
工程建设中的中标结果信息/中标候选人信息
完整的html中标信息
第一中标候选人
中标金额
中标时间
其它参与投标的公司
思路:
- 从首页打开一个公告,先尝试得到一个公告的信息。
- 在得到另一个公告的信息进行比较,只有ID是变化的,所以有了ID就可以批量爬取了。
- 回到首页,判断加载方式,确定数据的请求方式,url以及参数。
实现过程:
- 确认爬取的数据都是动态加载出来的
- 在首页中捕获到ajax请求对应的数据包,从该数据包中提取出请求的url和请求参数
- 对提取到的url进行请求发送,获取响应数据(json),(包含ID信息)
- 从json串中提取到每一个公告对应的id值
- 将id值和中标信息对应的url进行整合,进行请求发送捕获到每一个公告对应的中标信息数据
1 | # 该部分为得到一个公告的信息,但是我们这里的ID不灵活,需要进一步改进。 |
完成了对公告详情的爬取后,接下来批量爬取公告。
1 | # 该部分完成了对前5页的公告信息进行爬取 |
爬取图片
如何做?
- 基于requests
- 基于urllib
- 区别:urllib中的 urlretrieve 不可以进行UA伪装
requests在urllib基础上产生,更加pythonic!
基于requests的图片爬取
1 | import requests |
- request更具通用性,数据可以展示为:text(字符串),json(列表/字典),content(字节)。
基于urllib的图片爬取
1 | # 基于urllib |
- 由于urlretrieve不能做UA伪装,所以存在图片缺失的可能。
聚焦爬虫
较通用爬虫相比,增加了数据解析。
数据解析
概念:将一整张页面的局部数据进行提取/解析
作用:用来实现聚焦爬虫
实现方式:
- 正则:可以匹配任意,下面方式拿不到 js 代码中的数据
- bs4
- xpath
- pyquery
数据解析的通用原理是什么?
- 标签的定位
- 数据的提取
页面中的相关的字符串的数据都存储在哪里?
- 标签中间
- 标签的属性中
基于聚焦爬虫的编码流程
- 指定url
- 发起请求
- 获取响应数据
- 数据解析
- 持久化存储
正则解析
爬取煎蛋网中的图片
- 地址为:
http://jandan.net/pic/MjAxOTEwMDktNjk=#comments
实现过程:
- 指定url
- 获取响应数据
- 数据解析
- 写正则表达式
- 正则匹配
- 持久化存储
1 | import re |
note:内容依据响应数据page_text,而不是根据浏览器中网页上的代码,上面就出现一个字母的大小写不同而导致正则失效的例子。
bs4解析
环境的安装:
- pip install bs4
- pip install lxml
bs4的解析原理:
- 实例化一个BeatifulSoup的一个对象,把即将被解析的页面源码数据加载到该对象中
- 需要调用BeatifulSoup对象中的相关的方法和属性进行标签定位和数据的提取
BeatifulSoup的实例化
- BeatifulSoup(fp, ‘lxml’) 将本地存储的html文档中的页面源码数据加载到该对象中
- BeatifulSoup(page_text, ‘lxml’) 将从互联网中请求到的页面源码数据加载到该对象中
标签的定位
soup.tagName: 只可以定位到第一个tagName标签
属性定位:
- soup.find(‘div’,’attrName=’value’) 只能定位到符合要求的第一个
- soup.findAll:返回列表,可以定位到符合要求的所有标签
note:只有class需要加下划线,其它直接用原名就可以。
选择器定位:
- select(‘选择器’)
- 选择器:id,class,tag,层级选择器(大于号表示一个层级,空格表示多个层级)
取文本
- text:将标签中所有文本取出
- string:将标签中直系的文本取出
取属性
- tag[‘attrName’]
熟悉
此为练手的html。
1 | <html lang="en"> |
熟悉 BeautifulSoup 的选择器以及如何取文本和属性
1 | fp = open('./test.html',encoding='utf-8') |
爬取三国演义小说的标题和内容
- 地址为:
http://www.shicimingju.com/book/sanguoyanyi.html
数据解析流程:
- 首先定位标签
- 然后取出文本和内容
1 | main_url = 'http://www.shicimingju.com/book/sanguoyanyi.html' |
xpath 解析
环境的安装
- pip install lxml
解析原理
- 实例化一个etree的对象,且把即将被解析的页面源码数据加载到该对象中
- 调用etree对象中的xpath方法结合着不同形式的xpath表达式进行标签定位和数据提取
etree对象的实例化
- etree.parse(‘fileName’)
- etree.HTML(page_text)
标签定位
- 最左侧的/:一定要从根标签开始进行标签定位
- 非最左侧的/:表示一个层级
- 最左侧的//:可以从任意位置进行指定标签的定位
- 非最左侧的//:表示多个层级
- 属性定位:
- //tagName[@attrName=”value”]
- 索引定位:
- //tagName[@attrName=”value”]/li[2],索引是从1开始的
- 逻辑运算:
- 找到href属性值为空且class属性值为du的a标签
- //a[@href=”” and @class=”du”]
- 模糊匹配:
- //div[contains(@class, “ng”)]
- //div[starts-with(@class, “ta”)]
取文本
- /text():取直系的文本内容
- //text():取所有文本内容
取属性
- /@attrName
note:
- 返回的都是列表
- 在google中的Element可以直接copy xpath
- 在xpath表达式中不可以出现tbody标签
熟悉
依旧使用上面的html练手
1 | from lxml import etree |
爬取虎牙主播名称,热度和标题
1 | url = 'https://www.huya.com/g/xingxiu' |
爬取前5页的妹子
地址为:http://sc.chinaz.com/tag_tupian/yazhoumeinv.html
- 涉及中文乱码
- 以前对response对象设置 encoding 这样针对一整张页面代价大。
- 我们只需要对想要的内容进行转码。
- 通常由 iso-8859-1 –> utf-8 先编码成iso-8859-1 在解码成 utf-8 或gbk
- 多页码数据的爬取
- 制定一个通用的url模板
1 | # 通用模板 |
爬取全国所有城市的名称,包含热门城市和全部城市
待补充!!!
梨视频短视频的爬取
需求:爬取
https://www.pearvideo.com/category_8
这一页的一组视频视频的url是藏在script中,所以只能配合正则去解析。
1 | url = 'https://www.pearvideo.com/category_8' |
requests高级
- 下面介绍的本质上也是针对反爬机制做出的策略。
代理
概念:代理服务器
代理的作用:
- 请求和响应的转发(拦截请求和响应)
代理和爬虫之间的关联是什么?
- 可以基于代理实现更换爬虫程序请求的ip地址
代理ip的网站
- 西祠代理
https://www.xicidaili.com/nn
- 快代理
- www.goubanjia.com
- 代理精灵
- 西祠代理
代理的匿名度
- 高匿名:不知道是否使用代理服务器,也无法知道最终的ip
- 匿名:使用了代理服务器,但不知道最终ip
- 透明:
类型
- http:只能转发http协议的请求
- https:可以转发https的请求
chrome添加代理
- 设置搜索 代理
- 在连接下,点击局域网设置,点击代理服务器并进行配置。
目标:在requests使用代理
- proxies参数:
{ 'https' : 'ip:port' }
- proxies参数:
使用代理ip
- 我是在代理精灵买的,6块300个,每个可以用5-25分钟。
- 先尝试一个,然后爬取
百度搜索ip页面
保存到本地,查看ip是否发生变化。
1 | # 在http://www.jinglingdaili.com/ |
搭建付费的代理池
- 需求:还是前面的买的那些ip,给代理池中放50个ip
- 首先在代理精灵中生成API链接,有了这个链接,我们就可以直接爬取了
1 | 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=txt&ss=1&css=&dt=1&specialTxt=3&specialJson=&usertype=2' |
note:如果提取不到说句,说明你的xpath表达式不正确,我们参考page_text的输出进行编写。
搭建一个免费的代理池
- 需求:将 西祠代理 上面展示的 ip,端口以及类型进行爬取。
- 当然,这些数据其实还需要进行测试,待补充!!!
不使用代理,本机IP直接尝试,但是不到50页就会被封掉IP
1 | url = 'https://www.xicidaili.com/nn/%s' # 通用的url模板(不可变) |
为了爬取大量数据,我们借助上面搭建的代理池再次尝试!
1 | # 构建付费的代理池 |
cookie
爬虫中怎样处理cookie?
手动处理:
- 将cookie写在headers中,(不涉及有效时长以及动态变化)
自动处理:
- 使用session对象:requests.Session(),它也被称为 会话对象
- 会话对象让你能够跨请求保持某些参数。它也会在同一个 Session 实例发出的所有请求之间保持 cookie
- 作用:
- session对象和requests对象都可以对指定的url进行请求发送。只不过使用session进行请求发送的过程中,如果产生了cookie则cookie会被自动存储到session对象中。
- 使用session对象:requests.Session(),它也被称为 会话对象
爬取雪球网的新闻和内容
- 地址:
https://xueqiu.com
1 | # 错误尝试 |
有了这种提示,说明我们需要使用cookie了,为什么呢?因为你访问的这个地址并不是首页,而是在首页的基础上给后端发送 ajax 请求,所以有了基于cookie的反爬。
- 那我们该怎么做呢?是需要实例化 requests.Session() 对象,同requests的使用方式,进行使用。
1 | # 创建会话对象 |
这回就可以拿到数据了。但是如果由 cookie 检测的时候才会选择使用会话对象,毕竟存在效率问题。
验证码的识别
对于验证码我们大多使用在线打码平台,给大家推荐几个:
- 超级鹰(正在用)
- 云打码
打码平台(超级鹰)的使用:
- 注册
- 登录
- 创建一个软件,复制软件ID
- 下载示例代码《开发文档》
所以我们需要做的就是将验证码爬取下来,使用打码平台的API的到结果。
下面这个是超级鹰的内部实现:
1 | import requests |
将打码工具封装:
1 | def get_code(img_path,ctype): |
模拟登录
攻克了验证码来尝试一下模拟登录哈!
- 地址为:
https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx
1 | # 得到验证码的图片,并存储到本地 |
这里设计的坑:
- 验证码
- 动态参数 __VIEWSTATE 等是隐藏在前端页面源码中的
- 需要携带cookie才能成功,而设置cookie的地方却在请求验证码。
所以如果涉及cookie,觉得不确定就都用会话对象处理。
遇到的错误以及解决方式
超时
- headers中加 Connection:’close’
代理错误
ProxyError: HTTPSConnectionPool(host=’www.baidu.com', port=443): Max retries exceeded with url: /s?tn=80035161_2_dg&wd=ip (Caused by ProxyError(‘Cannot connect to proxy.’, OSError(‘Tunnel connection failed: 503 Service Unavailable’)))
- 我的代理并没有生效,这个代理是绑定本机ip的,但是我的ip不再白名单中。