Scrapy结合Redis实现增量爬取

Scrapy适合做全量爬取,但是,我们不是一次抓取完就完事了。很多情况,我们需要持续的跟进抓取的站点,增量抓取是最需要的。

Scrapy与Redis配合,在写入数据库之前,做唯一性过滤,实现增量爬取。

一、官方的去重Pipeline

官方文档中有一个去重的过滤器:

from scrapy.exceptions import DropItem

class DuplicatesPipeline(object):

    def __init__(self):
        self.ids_seen = set()

    def process_item(self, item, spider):
        if item['id'] in self.ids_seen:
            raise DropItem("Duplicate item found: %s" % item)
        else:
            self.ids_seen.add(item['id'])
            return item


官方的这个过滤器的缺陷是只能确保单次抓取不间断的情况下去重,因为其数据是保存在内存中的,当一个爬虫任务跑完后程序结束,内存就清理掉了。再次运行时就失效了。

二、基于Redis的去重Pipeline

为了能够多次爬取时去重,我们考虑用Redis,其快速的键值存取,对管道处理数据不会产生多少延时。

#pipelines.py

import pandas as pd
import redis
redis_db = redis.Redis(host=settings.REDIS_HOST, port=6379, db=4, password=settings.REDIS_PWD)
redis_data_dict = "f_uuids"

class DuplicatePipeline(object):
    """
    去重(redis)
    """

    def __init__(self):
        if redis_db.hlen(redis_data_dict) == 0:
            sql = "SELECT uuid FROM f_data"
            df = pd.read_sql(sql, engine)
            for uuid in df['uuid'].get_values():
                redis_db.hset(redis_data_dict, uuid, 0)

    def process_item(self, item, spider):

        if redis_db.hexists(redis_data_dict, item['uuid']):
             raise DropItem("Duplicate item found:%s" % item)

        return item
  1. 首先,我们定义一个redis实例: redis_db和redis key:redis_data_dict。
  2. 在DuplicatePipeline的初始化函数init()中,对redis的key值做了初始化。当然,这步不是必须的,你可以不用实现。
  3. 在process_item函数中,判断redis的hash表中存在该值uuid,则为重复item。
    至于redis中为什么没有用list而用hash? 主要是因为速度,hash判断uuid是否存在比list快好几个数据级。
    特别是uuid的数据达到100w+时,hash的hexists函数速度优势更明显。

最后别忘了在settings.py中加上:

# See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
    'fund_spider.pipelines.DuplicatePipeline': 200,
     #'fund_spider.pipelines.MySQLStorePipeline': 300,
}

三、总结

本文不是真正意义上的增量爬取,而只是在数据存储环节,对数据唯一性作了处理,当然,这样已经满足了大部分的需求。

当有一天需求变了,在你像mysql 数据库保存的时候,发现已经有一部分已经存在,有一部分新的数据,你又需要添加到mysql数据库中,这时候你就需要通过redis来作为中间件,通过url来确保爬过的数据不会再爬,做到增量爬取,
from redis import Redis
import pandas as pd
import pymysql

redis_db = Redis(host='your_setting', port=6379, db=4) #连接redis
redis_data_dict = "k_url"

class FirstScrapyPipeline(object):
    def __init__(self):
        self.connect = pymysql.connect(host='your_seting', port=3306, user=user,password='yourseting',database='yourpassword,'db='dbname',charset=vacher)
        self.cursor = self.connect.cursor()##这里我是先将数据存到mysql数据库,建立游标
      redis_db.flushdb()## 删除redis里面的key

        if redis_db.hlen(redis_data_dict) == 0:
            sql = "SELECT url FROM ts_dobn;
            df = pd.read_sql(sql, self.connect)# 读MySQL数据
            for url in df['url'].get_values():  # 把每一条的值写入key的字段里
                redis_db.hset(redis_data_dict, url, 0)
    def process_item(self, item, spider):
        if  redis_db .hexists (redis_data_dict, item['url']):  # 取item里的url和key里的字段对比,看是否存在,存在就丢掉这个item。不存在返回item给后面的函数处理
        raise DropItem("Duplicate item found: %s" % item)
        values = [item["title"],item['start'],item['desc'],item['url']]
        self.do_insert(values)
        self.connect.commit()
        return item
    def do_insert(self,values):
        sql = 'insert into ts_douban VALUES (0,%s,%s,%s,%s)'
        self.cursor.execute(sql,values)
    def close_spider(self,spider):
        self.connect.close()
因为数据库没内容,所以我先存数据进数据库,若你数据库本来就有数据,那么请忽略最后两个方法,


增量入库:

官方的去重比较简单,只要换成自己的item['#'],然后在settings里启用DuplicatesPipeline就可以了。

import mysql.connector
import pandas as pd  #用来读MySQL
import redis 
redis_db = redis.Redis(host='127.0.0.1', port=6379, db=4) #连接redis,相当于MySQL的conn
redis_data_dict = "f_url"  #key的名字,写什么都可以,这里的key相当于字典名称,而不是key值。


class DuplicatesPipeline(object):
    conn = mysql.connector.connect(user = 'root', password='yourpassword', database='dbname', charset='utf8')    

    def __init__(self):
        redis_db.flushdb() #删除全部key,保证key为0,不然多次运行时候hlen不等于0,刚开始这里调试的时候经常出错。
        if redis_db.hlen(redis_data_dict) == 0: #
            sql = "SELECT url FROM your_table_name;"  #从你的MySQL里提数据,我这里取url来去重。
            df = pd.read_sql(sql, self.conn) #读MySQL数据
            for url in df['url'].get_values(): #把每一条的值写入key的字段里
                redis_db.hset(redis_data_dict, url, 0) #把key字段的值都设为0,你要设成什么都可以,因为后面对比的是字段,而不是值。


    def process_item(self, item, spider):
        if redis_db.hexists(redis_data_dict, item['url']): #取item里的url和key里的字段对比,看是否存在,存在就丢掉这个item。不存在返回item给后面的函数处理
             raise DropItem("Duplicate item found: %s" % item)

        return item```


重点说一下,虽然redis是一个键值对应的数据库,但这里为了速度用的是哈希(hash),和一般的字典不一样,比一般的字典多了个字段。详细可以看这个[教程](http://www.runoob.com/redis/redis-hashes.html),重点看hexists,hset,hlen,hgetall这几个,在终端里运行一下就明白了。教程里都是命令行运行,和python里的用法稍微有点不一样。

redis哈希结构:
结构:key field(字段) value
对应:redis_data_dict   url(实际的url)   0(代码里设置成了0)
而item里key是'url' value是实际的url
######相当于用key(redis_data_dict)的字段(url)来对比item['url']的值,存在为1(true),不存在就是0(false),我开始也是这里晕的,后来打印出来才明白了。

如果要用:
把conn数据库连接换成自己的
修改sql语句,df['url']
然后把item['url']换成自己的,就可以了。

可以在程序中间加入print,把每一步打印出来,看数值是什么就容易懂了。

最后,感谢这位小伙伴的文章,帮我解决的问题~
####参考文章:
[Scrapy结合Redis实现增量爬取](http://www.jianshu.com/p/7b6c1754ee73)



猜你喜欢

转载自blog.csdn.net/fengxueersui/article/details/80568680