scrapy爬虫起步(5)--又一个多页面抓取的方法

时间:2021-08-03 08:44:59

2015-06-23更新:
之前写这篇的时候还没有遇到具体的场景,只是觉得有这样一种方法就记下来了。
今天刚好遇到这个需求,要抓取一个BBS的内容,板块主页只显示了文章标题、作者和发表时间,需要做的是根据时间过滤,抓取每天最新的内容。利用Rule规则进行URL提取后只保留了链接地址,而失去了上下文信息(发布时间),如果要过滤只能是在抓取链接内容之后再获取时间,实际上造成不必要的浪费。使用此篇文章的方法可以在抓取链接之前就过滤,避免了无用数据的抓取。


上上篇scrapy爬虫起步(3)– 利用规则实现多页面抓取给出了利用CrawlSpider的Rule实现多页面抓取的方法,实际上直接利用BaseSpider也能实现多页面抓取。
具体思路:还是以我们的豆瓣小组为例吧,豆瓣社科小组,我们将首页地址作为start_url参数,从页面源码找到其余分页,如下:

   <div class="paginator">
        <span class="prev">&lt;前页</span>         
        <span class="thispage" data-total-page="9">1</span>          
        <a href="http://www.douban.com/group/explore?start=20&amp;tag=社科" >2</a>   
        <a href="http://www.douban.com/group/explore?start=40&amp;tag=社科" >3</a>
        <a href="http://www.douban.com/group/explore?start=60&amp;tag=社科" >4</a>      
        <a href="http://www.douban.com/group/explore?start=80&amp;tag=社科" >5</a>
        <a href="http://www.douban.com/group/explore?start=100&amp;tag=社科" >6</a>
        <a href="http://www.douban.com/group/explore?start=120&amp;tag=社科" >7</a>
        <a href="http://www.douban.com/group/explore?start=140&amp;tag=社科" >8</a>
        <a href="http://www.douban.com/group/explore?start=160&amp;tag=社科" >9</a>
        <span class="next">
            <link rel="next" href="http://www.douban.com/group/explore?start=20&amp;tag=社科"/>
            <a href="http://www.douban.com/group/explore?start=20&amp;tag=社科" >后页&gt;</a>
        </span>
    </div>

可以提取出各分页的地址,进行多页面抓取的思路是将地址封装为Request,作为回调函数parse的一个返回值(不影响item返回值),并为这些分页地址指定对应的回调函数。由于首页和分页在形式上完全一样(首页本身也是一个分页),因此直接指定parse为回调函数即可。
代码如下:

from scrapy.spider import BaseSpider
from douban.items import DoubanItem
from scrapy.http import Request

class GroupSpider(BaseSpider):
    name = "douban"
    allowed_domains = ["douban.com"]
    start_urls = ["http://www.douban.com/group/explore?start=0&tag=%E7%A4%BE%E7%A7%91"]

    #默认的回调函数
    def parse(self, response):
        print "+"*20, response.url
        item = DoubanItem()
        sel = response.xpath("//div[@class='group-list']/div[@class='result']")
        for s in sel:
            info = s.xpath("div/div/h3/a/text()").extract()[0]
            item["groupName"] = info
            yield item

        #处理当前页面里的分页 -- 封装为Request返回
        sel = response.xpath("//div[@class='paginator']/a/@href").extract()
        for s in sel:
            print response.url.split("?")[1].split("&")[0], "->", s.split("?")[1].split("&")[0]
            yield Request(s, callback=self.parse)

前面用的都是CrawlSpider,这里换成了BaseSpider,不支持Rule;同时在原来的回调函数基础上增加了对分页的处理。首先是提取地址,然后打印语句,待会再说,最后返回Request。

写这段代码的时候感觉有个问题:由于每个分页指定的回调函数都是parse,相当于对每个分页都会进行一次分页地址提取,这样实际上最终每个分页地址都被返回了N次,那么scrapy在实际处理的时候会不会也把每个地址处理N遍呢?更严重的,会不会陷入死循环?例如从第一页里提取出第二页-》处理第二页,提取出第一页-》处理第一页,提取出第二页-》……

直接跑程序看看吧,现在看看我们的print语句:“->”前面代表当前页地址,后边是从当前页提取的分页地址,为了精简输出,只提取了用以标识分页的“start”参数。
运行结果如下:

class GroupSpider(BaseSpider):
++++++++++++++++++++ http://www.douban.com/group/explore?start=0&tag=%E7%A4%BE%E7%A7%91 start=0 -> start=20 start=0 -> start=40 start=0 -> start=60 start=0 -> start=80 start=0 -> start=100 start=0 -> start=120 start=0 -> start=140 start=0 -> start=160 ++++++++++++++++++++ http://www.douban.com/group/explore?start=40&tag=%E7%A4%BE%E7%A7%91 start=40 -> start=0 start=40 -> start=20 start=40 -> start=60 start=40 -> start=80 start=40 -> start=100 start=40 -> start=120 start=40 -> start=140 start=40 -> start=160 ++++++++++++++++++++ http://www.douban.com/group/explore?start=20&tag=%E7%A4%BE%E7%A7%91 start=20 -> start=0 start=20 -> start=40 ++++++++++++++++++++ http://www.douban.com/group/explore?start=60&tag=%E7%A4%BE%E7%A7%91 start=20 -> start=60 start=20 -> start=80 start=20 -> start=100 start=60 -> start=0 start=60 -> start=20 ++++++++++++++++++++ http://www.douban.com/group/explore?start=100&tag=%E7%A4%BE%E7%A7%91 ++++++++++++++++++++ http://www.douban.com/group/explore?start=80&tag=%E7%A4%BE%E7%A7%91 ++++++++++++++++++++ http://www.douban.com/group/explore?start=140&tag=%E7%A4%BE%E7%A7%91 start=20 -> start=120 start=20 -> start=140 start=20 -> start=160 start=60 -> start=40 start=60 -> start=80 start=60 -> start=100 start=100 -> start=0 start=100 -> start=20 start=80 -> start=0 start=80 -> start=20 start=140 -> start=0 start=140 -> start=20 ++++++++++++++++++++ http://www.douban.com/group/explore?start=160&tag=%E7%A4%BE%E7%A7%91 start=160 -> start=0 ++++++++++++++++++++ http://www.douban.com/group/explore?start=120&tag=%E7%A4%BE%E7%A7%91 start=120 -> start=0 start=60 -> start=120 start=60 -> start=140 start=60 -> start=160 start=100 -> start=40 start=100 -> start=60 start=100 -> start=80 start=80 -> start=40 start=80 -> start=60 start=80 -> start=100 start=140 -> start=40 start=140 -> start=60 start=140 -> start=80 start=160 -> start=20 start=160 -> start=40 start=160 -> start=60 start=120 -> start=20 start=120 -> start=40 start=120 -> start=60 ++++++++++++++++++++ http://www.douban.com/group/explore?start=0&tag=%E7%A4%BE%E7%A7%91 start=100 -> start=120 start=100 -> start=140 start=100 -> start=160 start=80 -> start=120 start=80 -> start=140 start=80 -> start=160 start=140 -> start=100 start=140 -> start=120 start=140 -> start=160 start=160 -> start=80 start=160 -> start=100 start=160 -> start=120 start=120 -> start=80 start=120 -> start=100 start=120 -> start=140 start=0 -> start=20 start=0 -> start=40 start=0 -> start=60 start=160 -> start=140 start=120 -> start=160 start=0 -> start=80 start=0 -> start=100 start=0 -> start=120 start=0 -> start=140 start=0 -> start=160

输出有点多,可以看出:
1. 每个分页地址确实被返回了多次;
2. 没有陷入死循环,没有重复处理;
3. 处理过程是乱序的;

这说明scrapy自身已经进行了去重处理,而且使用线程进行数据抓取操作。

Ok,任务完成。感觉还是利用CrawlSpider的Rule比较方便。