scrapy常见问题

1. 项目名称问题

在使用的时候遇到过一个问题,在初始化scrapy startproject tutorial的时候,如果使用了一些特殊的名字,如:testfang等单词的话,通过get_project_settings方法获取配置的时候会出错,改成tutorial或一些复杂的名字的时候不会

ImportError: No module named tutorial.settings

这是一个bug,在github上有提到:https://github.com/scrapy/scrapy/issues/428,但貌似没有完全修复,修改一下名字就好了(当然scrapy.cfgsettings.py里面也需要修改)

2. 为每个pipeline配置spider

上面我们是在settings.py里面配置pipeline,这里的配置的pipeline会作用于所有的spider,我们可以为每一个spider配置不同的pipeline,设置Spidercustom_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对象到调度器,达到顺序执行的目的,暂时没有更好的方案

猜你喜欢

转载自blog.csdn.net/python36/article/details/79770938