1. 项目名称问题
在使用的时候遇到过一个问题,在初始化scrapy startproject tutorial
的时候,如果使用了一些特殊的名字,如:test
, fang
等单词的话,通过get_project_settings
方法获取配置的时候会出错,改成tutorial
或一些复杂的名字的时候不会
ImportError: No module named tutorial.settings
|
这是一个bug,在github上有提到:https://github.com/scrapy/scrapy/issues/428,但貌似没有完全修复,修改一下名字就好了(当然scrapy.cfg
和settings.py
里面也需要修改)
2. 为每个pipeline配置spider
上面我们是在settings.py里面配置pipeline,这里的配置的pipeline会作用于所有的spider,我们可以为每一个spider配置不同的pipeline,设置Spider
的custom_settings
对象
class LianjiaSpider(CrawlSpider): ... # 自定义配置 custom_settings = { 'ITEM_PIPELINES': { 'tutorial.pipelines.TestPipeline.TestPipeline': 1, } } |
3. 获取提取链接的节点信息
通过LinkExtractor提取的scrapy.Link
默认不带节点信息,有时候我们需要节点的其他attribute属性,scrapy.Link
有个text
属性保存从节点提取的text
值,我们可以通过修改lxmlhtml._collect_string_content
变量为etree.tostring
,这样可以在提取节点值就变味渲染节点scrapy.Link.text
,然后根据scrapy.Link.text
属性拿到节点的html,最后提取出我们需要的值
from lxml import etree import scrapy.linkextractors.lxmlhtml scrapy.linkextractors.lxmlhtml._collect_string_content = etree.tostring |
4. 从数据库中读取urls
有时候我们已经把urls下载到数据库了,而不是在start_urls里配置,这时候可以重载spider的start_requests
方法
def start_requests(self): for u in self.db.session.query(User.link): yield Request(u.link) |
我们还可以在Request添加元数据,然后在response中访问
def start_requests(self): for u in self.db.session.query(User): yield Request(u.link, meta={'name': u.name}) def parse(self, response): print response.url, response.meta['name'] |
5. 如何进行循环爬取
有时候我们需要爬取的一些经常更新的页面,例如:间隔时间为2s,爬去一个列表前10页的数据,从第一页开始爬,爬完成后重新回到第一页
目前的思路是,通过parse方法迭代返回Request进行增量爬取,由于scrapy默认由缓存机制,需要修改
6. 关于去重
scrapy默认有自己的去重机制,默认使用scrapy.dupefilters.RFPDupeFilter
类进行去重,主要逻辑如下
if include_headers: include_headers = tuple(to_bytes(h.lower()) for h in sorted(include_headers)) cache = _fingerprint_cache.setdefault(request, {}) if include_headers not in cache: fp = hashlib.sha1() fp.update(to_bytes(request.method)) fp.update(to_bytes(canonicalize_url(request.url))) fp.update(request.body or b'') if include_headers: for hdr in include_headers: if hdr in request.headers: fp.update(hdr) for v in request.headers.getlist(hdr): fp.update(v) cache[include_headers] = fp.hexdigest() return cache[include_headers] |
默认的去重指纹是sha1(method + url + body + header),这种方式并不能过滤很多,例如有一些请求会加上时间戳的,基本每次都会不同,这时候我们需要自定义过滤规则
from scrapy.dupefilter import RFPDupeFilter class CustomURLFilter(RFPDupeFilter): """ 只根据url去重""" def __init__(self, path=None): self.urls_seen = set() RFPDupeFilter.__init__(self, path) def request_seen(self, request): if request.url in self.urls_seen: return True else: self.urls_seen.add(request.url) |
配置setting
DUPEFILTER_CLASS = 'tutorial.custom_filters.CustomURLFilter'
|
7. 如何在Pipeline中处理不同的Item
scrapy所有的迭代出来的的Item都会经过所有的Pipeline,如果需要处理不同的Item,只能通过isinstance()
方法进行类型判断,然后分别进行处理,暂时没有更好的方案
8. url按顺序执行
我们可以通过Request的priority控制url的请求的执行顺序,但由于网络请求的不确定性,不能保证返回也是按照顺序进行的,如果需要进行逐个url请求的话,吧url列表放在meta对象里面,在response的时候迭代返回下一个Request对象到调度器,达到顺序执行的目的,暂时没有更好的方案