索引
1st
起因
研究 Apache Solr 和 Elasticsearch 时缺少内容,于是以爬虫的方式获取资源。未来有需要将会学习使用 Apache Solr 或 Elasticsearch 实现企业级搜索引擎
Scrapy
python-scrapy是个快速实现爬虫的高层次框架,感谢python带来的无限可能。关于Scrapy,有兴趣的可以点这里:Scrapy
爬取目标
- cnxw.com.cn 苍南新闻
- cncn.gov.cn 苍南政府
- cnjyw.net 苍南教育网
- cnlx.gov.cn 灵溪政务
- cnhrss.gov.cn 苍南人力资源
- 0577cnw.com 苍南网
- cnlxw.cn 苍南灵溪网
- cnzgh.org 苍南总工会
- cnys.gov.cn 苍南宜山
- cnlg.cn 苍南龙港
- zjjx.gov.cn 金乡
- cngt.gov.cn 苍南国土资源
- cnkjj.gov.cn 苍南科技
- cnmz.gov.cn 苍南明政
- cnwmw.gov.cn 苍南文明网
- cangnan5.com 苍南在线
目标分析
对于新闻类网站,例如 cnxw.com.cn ,使用以下代码即可获取整站新闻:1
regex_cncn = '.*cncn.gov.cn/art/.*html'
对于使用了动态页面的站点,例如 cnjyw.net ,使用以下代码即可:1
regex_cnjyw = '.*cnjyw.net/.*aspx?id=.*'
对于 0577cnw.com、cangnan5.com和cnlg.cn ,它们使用相同的Discuz系列,可以使用类似如下的代码:1
regex_0577cnw = '.*0577cnw.com/.*html'
由于这几个站点都使用BBS发布数据,所以在制定URL规则的时候需要过滤除第一页之外的其他页面,可以使用类似如下的代码:1
regex_0577cnw = '.*0577cnw.com/.*-1-1.html'
Redis
Scrapy默认使用了深度优先进行爬取,因此它会以目标为顺序进行爬取。由于各个站点之间可能有重复的URL出现,需要使用指纹信息以避免重复采集。Redis是一个 Key-Value 数据库,并支持python。每次进行爬取页面之前会在Redis数据库查询该页面的URL是否存在,不存在则进行爬取。相关代码如下:1
2
3
4
5
6
7...
r = getRedis()
data = r.get(url)
if(data is None):
...
r.set(key, response.url)
...
关于指纹信息,最终采用对URL做MD5计算生成。代码如下:
1 | m = hashlib.md5() |
Goose Extractor
到目前为止,爬虫部分的设计已经结束,只要执行,它就会爬遍目标所有符合规则的页面,接下来还需要对页面进行解析。 Goose
Extractor 是一个Python的开源文章提取库。可以用它提取文章的文本内容、图片、视频、元信息和标签,从GitHub可以获取源码和示例。正如GitHub所描述的,只要如下代码即可获取页面的标题和正文:
1 | url = "http://bbs.0577cnw.com/thread-43205-1-1.html" |
可以从这里获取 Goose
Extractor 的相关信息:Goose
The End
至此,基于scrapy框架的爬虫基本实现。单机运行了几天,爬取了几十万的页面并解析,没有防BAN处理
2nd
房产网站信息爬取
几天前在微信朋友圈看到苍南房地产微站,想起之前写过一个关于房地产的爬虫。最开始以百度排行第一的www.cnfce.net作为目标,当作熟悉scrapy的作业
URL规则分析
以http://www.cnfce.net/sale/为分析目标,简称为 目标
目标 以分页的方式展示出售房源,设置爬取页面为:
http://www.cnfce.net/sale/page\d*.html
由于页面的有效范围为:[1,50],可以通过设置start_urls指定爬取页面:
for i in range(1,51):
start_urls.append("http://www.cnfce.net/sale/page%s.html" %i)
到这里,爬虫已经可以爬遍 目标 的所有出售房源。由于房源的详细信息还未获取到,还需要对爬到的页面进行解析,获取类似如下的信息:
- 区域
- 小区
- 房型
- 楼层
- 户型
- 装修
- 面积
- 单价
- 价格
- 日期
chrome开发者工具和选择器
scrapy通过选择器(seletors)提取数据,通过特定的 XPath 或者 CSS表达式来“选择”HTML文件中的某个部分。使用chrome开发者工具可以很方便的得到自己所需要的 XPath 。例如通过以下 XPath ,即可获取展示部分的div:
//*[@id=”main”]
通过:
xpath('//*[@id="mainn"]/ul[re:test(@class, "ul[0-1]")]')
获取展示部分中的所有记录的合集
使用以下代码既可遍历当前页面的所有记录:
records = response.xpath('//*[@id="mainn"]/ul[re:test(@class, "ul[0-1]")]')
for record in records:
......
通过下面代码,可以获取到各个字段的数值:
item['area'] = record.xpath('li[re:test(@class, "lifw[0-1]")]/a/text()').extract()
item['unitprice'] = record.xpath('li[re:test(@class, "lidj[0-1]")]/a/text()').extract()
item['price'] = record.xpath('li[re:test(@class, "lijg[0-1]")]/a/text()').extract()
item['date'] = record.xpath('li[re:test(@class, "lirq[0-1]")]/a/text()').extract()
......
运行效果
在硬件配置和网络条件尚可的情况下,1分钟内即可爬遍50个页面,获取2000+条记录,内容大志如下:
url,date,price,unitprice,area
/sale/10024038.html,2015-02-24,102.00万,8500元,灵溪镇
/sale/10023090.html,2015-02-22,116.00万,8854元,灵溪镇
/sale/10022669.html,2015-02-22,70.00万,5468元,灵溪镇
/sale/10019716.html,2015-02-14,243.00万,14294元,灵溪镇
/sale/10023935.html,2015-02-14,188.50万,13876元,灵溪镇
/sale/10023939.html,2015-02-14,105.00万,3750元,灵溪镇
/sale/10024011.html,2015-02-14,215.00万,7166元,灵溪镇
/sale/10022802.html,2015-02-14,158.00万,6076元,灵溪镇
/sale/10022803.html,2015-02-14,90.00万,9782元,灵溪镇
......
The End
如果有需求,可以深入爬取,进入某条记录的详细页面,例如:
sale/10024038.html
获取该房源的更详细的信息
3rd
论坛信息爬取
苍南网,苍南在线,龙港网均使用了Discuz系统,因此只要设计出一种爬虫即可实现这三个站点论坛数据的爬取。在服务器未做设置的情况下,可以轻易实现整站的爬取
URL规则分析
以 http://www.cangnan5.com/forum-143-1.html 为分析目标,简称为 目标 。
目标 为今日苍南板块,每个帖子分为主题、标题、作者等几个部分。本篇仅研究标题和帖子URL的获取,目标页面的url规则为:
http://www.cangnan5.com/forum-143-d*.html
截至本篇,页面的有效范围为:[1,206],因此可以通过设置start_urls指定爬取页面:
for i in range(1,206):
start_urls.append("http://www.cangnan5.com/forum-143-%s.html" %i)
到这里,爬虫可以爬遍今日苍南板块的所有页面了。接下来还需要对爬到的页面进行解析,获取类似如下的信息:
- 标题
- 帖子
- URL
chrome开发者工具和选择器
scrapy通过选择器(seletors)提取数据,使用特定的 XPath 或者 CSS
表达式来“选择”
HTML文件中的某个部分。使用chrome开发者工具可以很方便的得到自己所需要的XPath。例如通过以下XPath,即可获取每个页面所有帖子的集合:
//tbody[re:test(@id, "normalthread_\d+")]/tr/th/a[3]
使用以下代码既可遍历当前页面的所有记录:
records = response.xpath('//tbody[re:test(@id, "normalthread_\d+")]/tr/th/a[3]')
for record in records:
......
通过下面代码,可以获取到标题和帖子URL字段:
item['title'] = record.xpath('text()').extract()
item['url'] = record.xpath('@href').extract()
运行效果
为了避免不必要的流量,只最一个页面进行爬取,获得类似如下数据:
url,title
http://www.cangnan5.com/thread-38492-1-1.html,震惊!苍南渔民废弃的“老船木”卖出天价!
http://www.cangnan5.com/thread-38490-1-1.html,"苍南环卫工讨红包被拒,商家门前垃圾成堆!"
http://www.cangnan5.com/thread-38489-1-1.html,"苍南有个奇葩姓,姓"操""
http://www.cangnan5.com/thread-38487-1-1.html,"苍南小偷潜伏15小时,刀逼女主人破密码"
http://www.cangnan5.com/thread-38486-1-1.html,瑞安酒驾女汉子被查后当众脱裤子?
......
题外话
本地论坛的内容确实有点low
⊙﹏⊙‖∣°
4th
爬虫ban策略和反ban策略
关于为什么要有爬虫ban策略,可以参考此文:关于网络爬虫
之前分布爬取的时候因为设计的爬虫比较弱智,一天内爬取某站上百万次。反省了一下,出于道德考虑,我把爬虫运行的时间集中在深夜。
关于为什么别人把你的爬虫ban了之后需要有反ban策略,当然是因为需要获取有价值的信息
(#‵′)
关于robots协议
robots协议,请参考百度百科:robots协议
制定robots.txt可以阻止有道德的爬虫,例如google、百度…
而对于无品的爬虫,根本不遵循robots.txt的内容,就好像直接把别人家门踢开一样。
爬虫的防范
本篇所涉及的爬虫防范主要针对无品爬虫,毕竟对于google、百度等爬虫,简单robots.txt就可以将他们拒之门外
IP封锁
简单的说是将你的IP甚至IP段拉入黑名单,以此达到防范目的
IP访问频率限制
部分爬虫对网站的利益会大于其毙,通过爬虫身后的产品可以宣传自己,例如搜索引擎。此时,在保证允许爬虫爬取,可以通过IP访问频率限制来防止高并发下导致网站无法正常运行的情况。
User-Agent
用户代理
User-Agent,是指浏览器,它的信息包括硬件平台、系统软件、应用软件和用户个人偏好。服务器端可以通过分析User-Agent来获取爬虫行为。例如出现 baiduspider 的 User-Agent 代表百度爬虫曾经来访过。在对IP未做限制的情况下,可以通过分析 User-Agent 来判断哪些爬虫耍了流氓。
爬虫反ban策略
一般来说,对于一个网站,除非关闭服务,否则是没有完美的策略来防范爬虫的。Scrapy官方文档提到了集中防Ban的策略:Scrapy文档
除去费用来说,使用高度分布式的下载器是最理想的反ban策略
IP池
一般的服务器,会将比较频繁访问的IP拉入黑名单,导致爬虫无法正常访问该站点。通过IP池可以一定程度上解决该问题。
打个比方,IP池可以想象成一个拥有很多IP的池子。当你爬取某个站点时,随机从池子中获取一个IP作为代理,以达到防止被服务器认出的效果。
DOWNLOAD_DELAY设置
某些服务器会以每秒的访问量来识别爬虫,通过 DOWNLOAD\_DELAY ,可以设置爬虫延时
User-Agent池
使用 User-Agent 池,轮流选择之一来作为 User-Agent 。池中包含常见的浏览器的 User-Agent ,以达到骗过浏览器伪装自己的目的
The End
本篇纯属于搬砖文,ban和反ban的内容均来之网络
对于ban,目前 apache 和 nginx 均可实现相关功能
5th
代理IP的验证
进行反ban策略时,我们通过某种途径获取了一批代理IP,简称 IPs 。此时,还需要对 IPs 进行筛选,获取其中可用的IP集合作为爬虫的IP池。
IPs的获取
反ban的策略中,类似Crawlera的产品是很好的方案,但是通常需要付费授权,本篇主要讨论以免费的方式获取 IPs 。目前可以通过网络爬虫的方式对各个代理IP发布站进行抓取以获取 IPs ,或者通过商业软件的试用版获取 IPs
代理IP发布站
例如 http://www.xici.net.co/
等站点,对于编写爬虫获取代理IP,可以参考 1st
要修改部分
商业软件
No Hands Proxies 是一个很好的选择
IPs的筛选
遍历 IPs ,并设置代理访问百度,通过返回信息判断该IP是否有效。通过下面的代码可以轻松遍历 IPs:
for line in file:
ip_queue.put(line.rstrip())
通过下面的代码可以设置代理:
opener = urllib2.build_opener(urllib2.ProxyHandler({'http':'http://%s/'%ip}))
通过下面的代码访问百度:
request = urllib2.Request('http://www.baidu.com/')
response = opener.open(request, timeout=30)
可以使用response的url来判断代理是否有效:
response.geturl().find("baidu")
最终效果如下:
183.207.228.123:80: is valid!
111.13.12.216:80: is valid!
116.236.216.116:8080: is valid!
221.176.14.72:80: is valid!
218.5.74.174:80: is valid!
6th
分布式爬虫
在单机运行爬虫的时候,已经充分利用硬件资源,无法提高爬虫的效率,此时可以考虑分布式爬虫。可以这么理解,在打扫一间很大很大的房间时,已经双手甚至双脚都用了,此时可以叫其他人帮忙打扫以增加打扫速度。换做分布式爬虫而言,就是N台主机协同合作一个爬虫项目
分布式爬虫的实现方式
分布式爬虫首先要实现一个任务队列,该任务队列可以理解成对某个页面的访问。然后每个主机通过访问任务队列并执行相关任务,同时向任务队列添加新任务,即任务调度的过程。添加任务的时候会通过指纹信息来判断该任务是否已经被执行过。最后,当任务队列的长度为0时,结束爬虫。
Scrapy-redis
scrapy-redis 是一个实现Scrapy分布式的框架,可以从这里获取关于 scrapy-redis 的介绍。根据文档,安装 scrapy-redis 后,只要在原有Scrapy爬虫的基础上添加如下代码即可实现分布式:
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
SCHEDULER_PERSIST = True
同时我们需要通过如下代码指定Redis服务器的地址:
REDIS_HOST = '192.168.1.132'
REDIS_PORT = 6379
实例
本段取自scrapy-redis中的README
- 安装scrapy-redis
- 第一次运行爬虫并停止它:
1 | $ cd example-project |
- 再次运行恢复停止的爬虫:
1 | $ scrapy crawl dmoz |
- 启动一个或多个其他主机上的爬虫:
1 | $ scrapy crawl dmoz |
7th
学习资料
在 https://realpython.com 看到两篇关于Scrapy入门的文章写的很好
尝试翻译这两篇文章,鉴于语言水平的问题,字面意思和原文出入比较大,所以决定私藏
readthedocs 上提供了官方文档的中文版,感谢翻译者
提供几个较好的scrapy系列