完美解决scrapy只采集详情页

最近做项目使用scrapy,采用的是script的形式去写爬虫的,使用crawlspider去定义爬虫规则。
我先从数据库里面把我要读取的规则拿出来,采用的是单个频道写规则的形式,对每个列表页写一套规则,所以有next_page的规则。

sql = """SELECT id, weburl_id, name, allow_domains, start_urls, next_page,
      allow_url, extract_from, title_xpath, body_xpath, publish_time_xpath, source_site_xpath, enable
      FROM leiju_rules WHERE enable > 0"""

next_page分析形式是xpath提取链接,使用Rule不跟随(follow = False)。我的需求是我应该采集所有的页面,但是由于我想做成定时任务,就需要下次还使用这一套规则来运行爬虫,做到增量爬。这样的话就涉及到了内容的去重。开发的最开始,我才用的是pipline 通道去重,也就是当我产生一个request之后才开始判断这个内容我需要不需要,这样是很浪费服务器资源的。

if self.Redis.exists('url:%s' % item['url']):
    raise DropItem("Duplicate item found: %s" % item['url'])
else:
    self.Redis.set('url:%s' % item['url'], 1)
    return item

然后我修改成采用downloadMiddleware下载中间件去控制访问,因为downloadMiddleware是可以控制在request访问之前的,加上Redis的过滤。

if Redis.exists('url:%s' % response.url):
    raise IgnoreRequest("IgnoreRequest : %s" % response.url)
else:
    Redis.set('url:%s' % response.url, 1)
    return response

这时候又有一个问题了,就是我会把所有的访问都给记录下来,下次我连我写的起始start_urls都进不去了。本来寄托于从以下代码入手:

rule_list.append(Rule(LinkExtractor(restrict_xpaths=rule['next_page'])))

即希望通过next_page拿到下一页的链接,然后把这些链接排除掉,已经准备好了一个Redis库去存储这些url。

Filter = redis.StrictRedis(host='127.0.0.1', port=6379, db=2, password='pass123456')

然而实际在使用的时候发现我通过:

rule_list.append(Rule(LinkExtractor(restrict_xpaths=rule['next_page'])))

拿到的根本不是URL列表,而是一个Rule对象,里面包括了LinkExtarctor的Xpath规则。我甚至想通过response和xpath拿到链接之后在用redis存起来进行排除。
 
解决方法:
配合使用pipline和downloadMiddleware来轻松完成以上功能。
我先定义一个downloadMiddleware,用来拦截url,防止重复访问。

class IngoreHttpRequestMiddleware(object):
    """ 从下载器处开始拦截/过滤URL """
    def process_response(self, request, response, spider):
        if Redis.exists('url:%s' % response.url):
            raise IgnoreRequest("IgnoreRequest : %s" % response.url)
        else:
            # Redis.set('url:%s' % response.url, 1)
            return response

然后定义一个pipline,用来保存最终流入pipline的item URL。

class FilterUrlPipline(object):
    """ use redis to fileter url """
    def __init__(self):
        self.Redis = config.Redis

    def process_item(self, item, spider):
        self.Redis.set('url:%s' % item['url'], 1)
        return item

这样一来,我的一个列表页面的所有链接都进入到了downloadMiddleware中,但是通过pipline拿到了最终我要的终极页面,通过pipline保存item URL,然后下次的时候redis里面有了数据,就可以排除所有的符合的终极页面访问请求,这样就解决了。
 
已开源在github上面:https://github.com/fengxiaochuang/ScrapyDemo



十一月 24th, 2015 by