某东全网爬虫——scrapy_redis分布式

某东全网爬虫——scrapy_redis分布式

爬取京东的商品信息,从外层的分类,一步步深入获取商品的详情页信息。

环境:Python3.7

需求:

1、首页的分类信息:各级分类的名称和URL

2、商品信息:商品名称, 商品价格, 商品评论数量, 商品店铺, 商品促销, 商品选项, 商品图片等等

技术选择:

由于全网爬虫, 抓取页面非常多, 为了提高抓的速度, 选择使用scrapy框架 + scrapy_redis分布式组件

由于京东全网的数据量达到了亿级, 存储又是结构化数据, 所以数据库选择使用MongoDB

实现步骤

  1. 创建爬虫项目
  2. 根据需求, 定义数据数据模型
  3. 实现scrapy分类爬虫
  4. 保存分类信息
  5. 实现scrapy商品爬虫
  6. 保存商品信息
  7. 实现随机User-Agent和代理IP下载器中间件, 解决IP反爬
  8. 替换成scrapy_redis组件
  9. 部署到docker容器中,构建dockerfile,实现多服务器快速部署

采用广度优先策略, 我们把类别和商品信息的抓取分开来做

好处: 可以提高程序的稳定性

明确爬取数据

1、分类爬虫:

class Category(scrapy.Item):
    b_category_name = scrapy.Field()  # 大类别名称
    b_category_url = scrapy.Field()  # 大类别URL
    m_category_name = scrapy.Field()  # 中分类名称
    m_category_url = scrapy.Field()  # 中分类URL
    s_category_name = scrapy.Field()  # 小分类名称
    s_category_url = scrapy.Field()  # 小分类URL

2、商品爬虫:

class Product(scrapy.Item):
    product_category = scrapy.Field()  # 商品类别
    product_category_id = scrapy.Field()  # 商品类别ID
    product_sku_id = scrapy.Field()  # 商品ID
    product_name = scrapy.Field()  # 商品名称
    product_url = scrapy.Field()  # 商品详情页URL
    product_img_url = scrapy.Field()  # 商品图片URL
    product_book_info = scrapy.Field()  # 图书信息, 作者, 出版社
    product_option = scrapy.Field()  # 商品选项
    product_shop = scrapy.Field()  # 商品店铺
    product_comments = scrapy.Field()  # 商品评论数量
    product_ad = scrapy.Field()  # 商品促销
    product_price = scrapy.Field()  # 商品价格

分类爬虫

分析过程简单,不做说明

# -*- coding: utf-8 -*-
import scrapy
import json
from mall_spider.items import Category


class JdCategorySpider(scrapy.Spider):
    name = 'jd_category'
    allowed_domains = ['3.cn']
    start_urls = ['https://dc.3.cn/category/get']

    def parse(self, response):
        # print(response.body.decode('GBK'))
        result = json.loads(response.body.decode('GBK'))
        datas = result['data']
        # 遍历数据列表
        for data in datas:
            item = Category()
            b_category = data['s'][0]
            b_category_info = b_category['n']
            # print('大分类: {}'.format(b_category_info))
            item['b_category_name'], item['b_category_url'] = self.get_category_name_url(b_category_info)
            # 中分类信息列表
            m_category_s = b_category['s']
            # 遍历中分类列表
            for m_category in m_category_s:
                # 中分类信息
                m_category_info = m_category['n']
                # print('中分类: {}'.format(m_category_info))
                item['m_category_name'], item['m_category_url'] = self.get_category_name_url(m_category_info)

                # 小分类数据列表
                s_category_s = m_category['s']
                for s_category in s_category_s:
                    s_category_info = s_category['n']
                    # print('小分类: {}'.format(s_category_info))
                    item['s_category_name'], item['s_category_url'] = self.get_category_name_url(s_category_info)
                    # print(item)
                    # 把数据交给引擎
                    yield item

    @staticmethod
    def get_category_name_url(category_info):
        """
        根据分类信息, 提取名称和URL
        :param category_info:  分类信息
        :return: 分类的名称和URL
        分析数据格式(三类数据格式)
        - book.jd.com/library/science.html|科学技术||0
        - 1713-3287|计算机与互联网||0
          - Https://channel.jd.com/{}.html
        - 9987-12854-12856|屏幕换新||0
          - Https://list.jd.com/list.html?cat={}
          - 把 - 替换为逗号, 然后填充到占位的地方.
        """
        category = category_info.split('|')
        # 分类URL
        category_url = category[0]
        # 分类名称
        category_name = category[1]
        # 处理第一类分类URL
        if category_url.count('jd.com') == 1:
            # URL进行补全
            category_url = 'https://' + category_url
        elif category_url.count('-') == 1:
            # 1713-3287|计算机与互联网||0
            category_url = 'https://channel.jd.com/{}.html'.format(category_url)
        else:
            # 9987-12854-12856|屏幕换新||0
            # 把URL中 `-` 替换为 `,`
            category_url = category_url.replace('-', ',')
            # 补全URL
            category_url = 'https://list.jd.com/list.html?cat={}'.format(category_url)

        # 返回类别的名称 和 URL
        return category_name, category_url

分类信息数据先存储到MongoDB

from pymongo import MongoClient

from mall_spider.spiders.jd_category import JdCategorySpider
from mall_spider.settings import MONGODB_URL


class CategoryPipeline(object):
    def open_spider(self, spider):
        """当爬虫启动的时候执行"""
        if isinstance(spider, JdCategorySpider):
            # open_spider方法中, 链接MongoDB数据库, 获取要操作的集合
            self.client = MongoClient(MONGODB_URL)
            self.collection = self.client['jd']['category']

    def process_item(self, item, spider):
        # process_item 方法中, 向MongoDB中插入类别数据
        if isinstance(spider, JdCategorySpider):
            # self.collection.insert_one(dict(item))
            # 以s_category_name过滤更新
            self.collection.update({'s_category_name': item['s_category_name']}, dict(item), True)
        return item

    def close_spider(self, spider):
        # close_spider 方法中, 关闭MongoDB的链接
        if isinstance(spider, JdCategorySpider):
            self.client.close()

商品爬虫

商品详情页的基本信息可以用fiddler抓包手机端数据获取,因为是json格式,解析简单。

对应的请求头要带上手机端的UA。

代码不放了

商品爬虫实现分布式

1、修改爬虫类

步骤:

1、修改继承关系: 继承RedisSpider

2、指定redis_key

3、把重写start_requests 改为 重写 make_request_from_data

from scrapy_redis.spiders import RedisSpider
import pickle

#  1. 修改继承关系: 继承RedisSpider
class JdProductSpider(RedisSpider):
    name = 'jd_product'
    allowed_domains = ['jd.com', 'p.3.cn']
    # 2. 指定redis_key
    redis_key = 'jd_product:start_category'

    # 3. 把重写start_requests 改为 重写 make_request_from_data
    def make_request_from_data(self, data):
        # 把从Redis中读取到分类信息, 转换为字典
        category = pickle.loads(data)
        return scrapy.Request(category['s_category_url'], self.parse, meta={'category': category})

注意: 在make_request_from_data不能使用 yield 必须使用 return

2、在settings文件中配置scrapy_redis

# MongoDB数据库的URL
MONGO_URL = 'mongodb://127.0.0.1:27017'

# REDIS数据链接
REDIS_URL = ' redis://127.0.0.1:6379/0'

# 去重容器类: 用于把已爬指纹存储到基于Redis的set集合中
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 调度器: 用于把待爬请求存储到基于Redis的队列
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 是不进行调度持久化:
# 如果是True, 当程序结束的时候, 会保留Redis中已爬指纹和待爬的请求
# 如果是False, 当程序结束的时候, 会清空Redis中已爬指纹和待爬的请求
SCHEDULER_PERSIST = True

3、redis_key脚本程序

写一个程序用于把MongoDB中分类信息, 放入到爬虫redis_key指定的列表中

from redis import StrictRedis
from pymongo import MongoClient
import pickle

from mall_spider.settings import MONGO_URL, REDIS_URL
from mall_spider.spiders.jd_product import JdProductSpider

# 把MongoDB中分类信息, 添加到Redis中
def add_category_to_redis():
    # 链接MongoDB
    client = MongoClient(MONGO_URL)
    # 链接Redis
    redis = StrictRedis.from_url(REDIS_URL)

    cursor = client['jd']['category'].find()
    # 读取MongoDB中分类信息, 序列化后, 添加到商品爬虫redis_key指定的list
    for category in cursor:
        redis.rpush(JdProductSpider.redis_key, pickle.dumps(category))

    # 关闭MongoDB的链接
    client.close()

if __name__ == '__main__':
    add_category_to_redis()

爬虫措施

在middlewares.py中间件中实现RandomUserAgent类和ProxyMiddleware类

User-Agent列表网上随便找

IP池根据需求,可以使用相关redis+flask实现的proxy pool,增加付费代理爬虫

或者使用阿布云之类的第三方动态http隧道代理

scrapy_redis空跑问题可以定义扩展类来解决

部署

推荐使用docker部署到服务器,利用k8s进行管理

使用gerapy实现多服务器调度管理

最后

正常使用scrapy调整并发数,多服务器运行,实现分布式增量爬虫。

爬取速度受限于IP池,一天百万数据问题不大。

千万和上亿级数据,要考虑对接布隆过滤器,以及对数据库进行调优。

代码

GitHub:

Scrapy Redis是一个用于分布式爬取网页的Python框架。它是基于Scrapy框架的扩展,采用Redis作为分布式队列,可以在多个爬虫节点之间共享任务和数据。通过引入scrapy_redis.spider文件中的RedisSpider类,可以将原来继承的scrapy.Spider类改为RedisSpider类,从而实现对分布式爬虫的支持。 在使用分布式爬虫的过程中,首先需要将项目配置为分布式,并将项目拷贝到多台服务器中。然后启动所有的爬虫项目,这样每个爬虫节点都可以独立运行。接下来,在主redis-cli中使用lpush命令将需要爬取的网址推送到Redis队列中。这样,所有的爬虫节点都会开始运行,同时获取不同的任务和数据,实现分布式爬取的效果。 要使用Scrapy Redis进行分布式爬取,首先需要安装scrapy_redis包。可以通过在CMD工具中执行命令"pip install scrapy_redis"来进行安装。安装完成后,就可以在项目中使用scrapy_redis进行分布式爬取了。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Scrapy基于scrapy_redis实现分布式爬虫部署](https://blog.csdn.net/baoshuowl/article/details/79701303)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值