用了一段时间的scrapy了,比直接Requests、Urllib确实是好用不少,框架仍是不错的,偶然看到这篇帖子,确实是深有体会,copy下做为记录php
在编程语言的世界里,python彷佛被贴上了作爬虫的一个标签,强而有力。而scrapy作为另外一个老牌的开源项目,更是大规模抓取不可或缺的一个重要力量。纵使scrapy依旧有一些长期没法解决的诟病,可是他在抓取过程帮程序员解决的一系列的细节问题,仍是有无以伦比的优点。html
scrapy依赖于twisted,而twisted是python世界的出名的重量级框架。要读懂scrapy的源码,必须先了解twisted(deffered,reactor)的工做原理,读懂twisted的源码,这样一切看起都很是困难。python
为了防止重复对同一个url抓取,避免无限递归的遍历url,scrapy把全部处理过的request(包括method,url,headers)放到内存当中,若是spider处理的request够多的话,spider占用的内存是惊人的。react
解决方法程序员
必要的时,将一个大型spider(可预计抓取的url过多)拆分红多个spider。ajax
bloom,以空间换取准确性的一种去重算法。settings.py里配置DUPEFILTER_CLASS = ‘scrapy.dupefilter.RFPDupeFilter’,用本身实现的bloom算法覆盖便可。要注意的是,bloom是牺牲了准确性开换取空间开支小的算法,在内存可以胜任,就显得画蛇添足。redis
常常会遇到解析一些非标准的html,在浏览器会对他们进行容错处理,而不会影响到页面展示。可是用xpath就要了命了,并且一般还不容易察觉。解决办法算法
将整个html转化成标准的xml,在用xpath解析。好比数据库
用re解析抽取结构化信息。不推荐用re,不是由于在这种场景下效果很差,而是由于用正则会增长spider的维护成本,具体的下面会提到。编程
理论只是实践基础和指导原则,要知道理论到实践仍是有不少路要走的。本人比较反对将重复造轮子的东西轻易的拿到生产上去实践。spider不只仅是把内容结构化,而是在各个细节处理上都很全面。
scrapy会从meta中自动提取里涉及到的编码,若是没有则尝试gb2312,utf-8编码,最后还不行的话,就用自定义的编码DEFAULT_RESPONSE_ENCODING。虽然这种方式不能完成正确,也能保证90%(我实践中获得的结果)以上的正确性。
pipeline以管道的方式处理item,好比说item再加工,过滤,持久化均可以再这里处理。能够定义多个pipeline,对item作不一样的处理。若是在item中有个image字段,能够先用MediaPipeline处理过,将图片下载到本地,再讲item插入到数据库。
scrapy是xpath做为解析工具,以前提到的也能够用正则可是不推荐,一个很重要的缘由是,xpath维护,可读性要比正则强太多。spider的维护一项持久而耗时的工做,特别是一些静态网站,都是经过cms系统发布,这样会致使网站的结构,样式调整的比较频繁,用re会陷入无尽的烦恼当中。而xpath当然也会有种问题,可是良好的可读性,会让维护成本成倍的下降。
设置抓取时间间隔,在spider中设置download_delay=x(单位是秒)
配置代理,settings中增长HttpProxyMiddleware(默认),设置系统代理
配置自定义代理
1 import base64
2
3 class ProxyMiddleware(object):
4 def process_request(self, request, spider):
5 request.meta['proxy'] = "http://YOUR_PROXY_IP:PORT"
6 proxy_user_pass = "USERNAME:PASSWORD"
7 encoded_user_pass = base64.encodestring(proxy_user_pass)
8 request.headers['Proxy-Authorization'] = 'Basic ' + encoded_user_pass
也能够将代理配置成随机的,只须要在上面代码中稍做处理。
http code的处理,正常状况下,scrapy的response只处理20x,设置handle_httpstatus_list = [301,302,204,206,404,500]
retry机制,因为网络或者对方服务器的缘由,对url重复处理是很是有必有,spider中设置RETRY_TIMES,RETRY_HTTP_CODES
模拟浏览器行为,设置user-agent
设置默认headers, DEFAULT_REQUEST_HEADERS
cookies处理,开启COOKIES_DEBUG=True后,能够再Request中带上cookies
yield Request(url=’http://www.douban.com‘,cookies={‘session’:’xxxx’})
等等吧,太多了。
默认开启TELNETCONSOLE_ENABLED = 1,WEBCONSOLE_ENABLED = 1具体的能够看文档。通常的状况下,会查看spider close的日志,整个spider的运行状态都查看到
下面接着写:
断断续续的使用scrapy已经很长时间,在各类问题也算是有所领悟(所需工具firefox,firebug,firefox的xpath插件)。
这个问题蛋疼不是问题有多难解决,而是很难定位到错误的缘由。在firefox中确保xpath没有写错的状况下,找不到相应的数据,这个时候就要考虑是不是这个缘由,直接查看页面源码,若是有不标准的html,在firefox中会有红色标识。
selector = XPathSelector(text=response.body.replace('<div class="left-clear"/>','<div class="left-clear">'))
一般的状况下,一些流量比较大的网站都有反爬虫的机制,避免恶意的访问,减轻服务器的压力。通常的状况的下调整抓取的间隔,更换代理。2种方式都有缺点。
抓取间隔设置download_delay=1,一般设置这个属性,整个spider的性能变得很是低。
更换代理,能够到http://pachong.org/去找免费的代理,通常速度也不快,并且不稳定。
通常状况数据的正常返回都是20x,scrapy会自动忽略掉50x的http code。这个(http://www.travelplus.cn/plus/list.php?tid=8
)用浏览器打开的时候,你看不出任何问题。用firebug就能清晰的看到,其实它是将500页面,当作正常页面显示。
在spider里添加handle_httpsatstus_code = [500],一切照旧便可。
若是你有服务器点开发的经验,就不难理解,服务器能够经过任何一个headers或者参数来屏蔽你的request。参数天然不用说,能带上都带上,至于一些参数加密,之后再提。主要是针对headers中的几个经常使用的。
Cookie,这个最多见用的,平心而论scrapy对cookie的支持只是基础的支持,用起来不太好用。
User-Agent, 这个主要仍是服务器为了区分request是来自pc端仍是手机端,会致使response不同。
Referer, 防止伪造的跨网站请求。
X-Requested-With=XMLHttpRequest, 这个很好理解,对于同一个url,ajax request和普通request的response结果不同很正常
Content-Length, 一些网站没有这个header会返回411
以上5个header基本上能解决问题,万一遇到顽固份子,那只好完全的模仿吧。
部分网站在提交数据的时候,会先跳转到一个页面,这个页面是空白的页,只是包含一些隐藏的表单,最后用js带上表单跳转到其余的页面。所以在firebug的监控的request中,会莫名其妙的多出来一些参数。
能简单尽可能简单,尽可能class和id来表达。千万别将xpath的表达式依赖不少属性,这样难以维护不说,并且极不稳定。
xpath表达式能在浏览器中找到元素,在spider里确不能,有可能性js动态加载,还有table元素,有的会自动添加th等。这个时候请直接对着html源码写你的表达式。
要关注的真正获得数据的request,一般咱们要抓取的数据也都在ajax request中,拿到数据后用json.loads(response.body)
def parse_city_item(self, response):
x = HtmlXPathSelector(response)
item = Item()
//给item赋值
item['title'] = ''.join(x.select('//div[@class="title"]/text()').extract())
data = {}
req = FormRequest(url=url,formdata=data,callback=self.parse_comment)
req.meta['item'] = item
//带上返回req
return req
def parse_comment(self, response):
item = response.request.meta['item']
x = HtmlXPathSelector(response)
item['content'] = ''.join(x.select('//div[@class="content"]/text()').extract())
return item
不少状况下须要抓取网站更新的内容。咱们知道在一次抓取的过程是能避免重复抓取,scrapy默认提供文件存储的方式,只须要再settings里设置JOBDIR=”path”。我在使用scrapy仍是0.9,没有这个特性,使用redis做为url存储。我的感受对于大规模抓取用redis仍是比文件的方式要好不少。redis里能够设置key的过时时间,确定会有人说,这样能保证不重复的抓取吗,固然不能绝对,可是能够调整抓取深度,对于抓取较为频繁网站,抓取到相同的几率就很低。好比说抓取sina的体育新闻,将url作md5加密存储到redis里,过时时间设置为1天,抓取体育新闻滚动页面前3页 点击连接,15分钟抓取一次。而单纯用文件方式存储的话,文件只大不小,多了天然影响性能。
上一篇也提到过。scrapy为咱们作了编码,可是若是这样好错了,就须要特殊处理了。
参考自:http://www.cnblogs.com/twelfthing/articles/4620533.html
http://www.cnblogs.com/twelfthing/articles/4620761.html