很久没有写新的内容了,看最近的一篇 都有2/3个半年了 最近又看了一些爬虫文章 还是想深入研究下
- 最近工作中有个需求是抓一些数据 数据量上万 讲真 以前都是玩玩 没实战 所以来这个需求的时候 我内心其实有点激动
然后 我开始装逼了 用request + BeautifulSoup(html) 用这两货实现
requests请求网络数据加载
BeautifulSoup(html) 自在解析器 解析网络获取数据啪啦啪啦啪啦啪啦 没几下代码撸完了 顿时感觉登上人生巅峰
开始运动 哦 不对 是运行 结果这货第一页(20个详情)数据 用了快4分钟
要不是中途有些log打印 我都以为他死了
我一想 这么不行啊 我特么600页的数据 这能玩
然后我就想 每次数据获取 一整个套路下来
就是 请求--->IO读取--->解析
能优化速度的地方 就是 IO读取 解析
有方向 那就开始 说说解析
本来用的BeautifulSoup 自带的解析器 文档上说了 速度慢
既然说了速度慢 肯定有速度快的 我就看了一下 lxml速度快 需要安装C语言库
解析属于高度计算 对于IO密集型与CPU密集来讲 属于CPU密集型任务 越底层的语言 越有优势 所以这里C语言会很快然后就开始安装 lxml
果不其然 报错了 当时在公司做 是Windows 说是缺少一个啥子 具体不记得(反正是微软的 )
如果Windows安装出相关问题 可以先用pip install wheel安装这个 然后下载lxml相关的whl文件 直接下载下来安装就行
这里要选择版本 什么的
相关whl下载地址 进去 ctrl+F搜索就行 注意版本号 和你Py版本 34 35 27 啥的
http://link.zhihu.com/?target=http%3A//www.lfd.uci.edu/~gohlke/pythonlibs/%23lxml一顿折腾lxml终于安装OK
然后 requests+BeautifulSoup(lxml) 在运动 一页50S
当时我就给吓尿了 效率直接快10倍号码
真是 ‘搏一搏单车 变摩托’
- 然后 我又感觉我登上了人生巅峰 电脑挂着 下班回家
第二天一脸懵逼 说好的数据呢 中途不知道咋的 卡着不动了 一晚上没抓完 本来以为一晚上10个小时差不多能抓完的 结果来这么一出
然后 想着 我总不能又跑一次 等10个小时吧 没那闲功夫看着啊
然后 想着能不能再IO等待那里做处理 我想着请求过去了 本地堵塞等着 要是等待的时候 干点别的 比如 开启另外的请求 这样是不是效率很高很多
然后就查了一下 多线程 多进程
结果整出来gevent 异步框架 里面用的协程(也是单线程 不过可以跳度切换 任务)
然后加上这个gevent之后
卧槽 ----直接每页变成 10S
- 最后完成那600页数据的读取 在这个基础还加上了 进程池pool
200页一个进程 反正最后 3 40分钟 就拿到了12000条数据
夜深了 就是容易 说废话 铺垫做完了 现在 来讲讲这些东西
-
requests 比起自带的urllib2啥的 方便很多 这个没啥说的 可以自己去看看
-
gevent 异步框架 今天也是刚用 用法也简单 等会看代码
-
BeautifulSoup 超级6 叼炸天 去从网页数据中获取你要的数据 前面用Xpath我觉得好用(主要还是最开始用re) 现在这个是首选这里也是这篇博客 主要介绍的 用糗事百科 的数据 来看看咋使用select 和 find
-
这里先贴两张图 糗事百科 数据获取的50页数据 的情况 find VS select 貌似差不多 没做详细对比 不过我感觉还是find快些吧 毕竟select返回list 大数据量下 还是要测试一波 两个都挺好用的 看习惯
然后一看也有106秒左右 为什么也不快 因为 没用代理IP(今天去相关网站找的一些 用进去很慢 而且很多不能用 先放着) 然后如果访问太快 直接返回我503 所以 每请求一页 sleep(1) s 也就是说 理论上 如果不停这1S我50页应该是最多66秒左右能跑完 也就是平均一页1S多一点
就算现在停了 在异步和lxml的处理下 大概也就2S一页 其实算可以啦
-
再贴一记 打印内容的
- 全部代码图 一点点 (后面 放github后会更新文章)
__author__ = 'Daemon1993'
import gevent
import requests
import time
from bs4 import BeautifulSoup
from bs4 import SoupStrainer
SP = 1
Count=0
def getHtmlByFind(baseurl, page):
url = baseurl + str(page)
r = requests.session()
html = r.get(url, timeout=5)
#如果状态不正确 这里目的503 暂停时间增大一点
if (html.status_code != 200):
global SP
SP += 0.5
return
#减少内存压力 取出我们需要的那块 不用全部
only_div_tag = SoupStrainer(id="content-left")
# 先拿到这一块
data = BeautifulSoup(html.text, "lxml",parse_only=only_div_tag)
for tag in data.find_all("div", class_="article block untagged mb15"):
name=tag.find("img").get('alt')
content=tag.find("div",class_="content").text.strip()
global Count
Count+=1
pass
# print("\\n 用户:{0} \\n {1}".format(name,content))
#每请求一次 睡眠一下
time.sleep(SP)
def getHtmlBySelect(baseurl, page):
url = baseurl + str(page)
r = requests.session()
html = r.get(url, timeout=5)
if (html.status_code != 200):
global SP
SP += 0.5
return
#减小内存压力
only_div_tag = SoupStrainer(id="content-left")
# 先拿到这一块
data = BeautifulSoup(html.text, "lxml",parse_only=only_div_tag)
for tag in data.select('div[class="article block untagged mb15"]'):
name=tag.select('img')[0].attrs.get('alt')
content=tag.select('div[class="content"]')[0].get_text().strip()
global Count
Count+=1
print("\\n 用户:{0} \\n\\n {1}".format(name,content))
#每请求一次 睡眠一下
time.sleep(SP)
def useFind(baseurl):
start=time.time()
global Count
Count=0
tasks = [gevent.spawn(getHtmlByFind, baseurl, index) for index in range(1, 50)]
gevent.joinall(tasks)
elapsed=time.time()-start
print('getHtmlByFind time {0} size{1}'.format(elapsed,Count))
def useSelect(baseurl):
start=time.time()
global Count
Count=0
#gevent.spawn 加入任务方法
tasks = [gevent.spawn(getHtmlBySelect, baseurl, index) for index in range(1, 50)]
#全部加入队列开始 处理
gevent.joinall(tasks)
elapsed=time.time()-start
print('getHtmlBySelect time {0} size{1}'.format(elapsed,Count))
if __name__ == '__main__':
baseurl = "http://www.qiushibaike.com/8hr/page/"
#useFind(baseurl)
useSelect(baseurl)
-
主要看看 BeautifulSoup 的用法
先分析 网页结构 如何拿到我们想要的 1是每一个 2是有头像地址和名字 3是content内容
如下图 取每个1的 2 3就行
-
find实现
跟就结构 分析 大题思路
1处是一个列表 ---> find_all("div",class__="className")--List
然后对每个集合中取出 img 和 div[class="content"]的内容 代码如下
for tag in data.find_all("div", class_="article block untagged mb15"):
name = tag.find("img").get('alt')
content = tag.find("div", class_="content").text.strip()
-
select实现
这里要先记住一点 select 每次返回都是list 在你能控制的住的情况下取[0]
同样分析 你会发现其实差不多 只是写法上有些不同 可以仔细看看两者的写法
for tag in data.select('div[class="article block untagged mb15"]'):
name = tag.select('img')[0].attrs.get('alt')
content = tag.select('div[class="content"]')[0].get_text().strip()
- 文档很多 但是下面这个我觉得看着最舒服BeautifulSoup 相关文档
下面图片所示的好像是正规文档
不知道为啥 也许是配色 看的我头晕
正规军团