Python爬虫学习记录——20.京东商品数据抓取

CrawlSprider类

什么是CrawlSpider
CrawlSpider 是爬取那些具有一定规则网站的常用的爬虫,因其定义了一些规则(rule),提供更加方便的跟进link的机制。它可能并不总是适用于某个特定网站或者项目,但是你可以从CrawlSpider出发,重写一些方法使得CrawlSpider更加适用于你的项目

CrawlSpider是Spider的派生类,Spider类的设计原则是只爬取start_url列表中的网页,而CrawlSpider类定义了一些规则(rule)来提供跟进link的方便的机制,从爬取的网页中提取符合规则的link,适用于大规模抓取

CrawlSpider的特点

  • 爬取一般网站常用的spider类

  • 它定义了一些 规则(rules) 来提供跟进link的方便的机制

  • 也许该spider并不是完全适合特定网站或项目,但其对很多情况都适用。 因此可以以其为起点,根据需求修改部分方法

  • 当然也可以实现自己的spider

独特的rules属性
CrawlSpider除了从Spider继承过来的(必须提供的)属性外,其提供了一个新的属性:

rules: 是Rule对象的集合,用于匹配目标网站并排除干扰,每个 Rule 对爬取网站的动作定义了特定表现。

Tips:如果多个rule匹配了相同的链接,则根据他们在属性中被定义的顺序,第一个会被使用

因为rules是Rule对象的集合,所以这里也要介绍一下Rule。它有几个参数:link_extractor、callback=None、cb_kwargs=None、follow=None、process_links=None、process_request=None,其中的link_extractor既可以自己定义,也可以使用已有LinkExtractor类

主要参数为:

  • link_extractor:是一个Link Extractor对象,用于定义需要提取的链接

  • callback: 从link_extractor中每获取到链接时,参数所指定的值作为回调函数,该回调函数接受一个response作为其第一个参数
    注意:当编写爬虫规则时,避免使用parse作为回调函数。由于CrawlSpider使用parse方法来实现其逻辑,如果覆盖了 parse方法,crawl spider将会运行失败

  • follow:是一个布尔值(boolean),指定了根据该规则从response提取的链接是否需要跟进。 如果callback为None,follow 默认设置为True ,否则默认为False

  • process_links:指定该spider中哪个的函数将会被调用,从link_extractor中获取到链接列表时将会调用该函数。该方法主要用来过滤

  • process_request:指定该spider中哪个的函数将会被调用, 该规则提取到每个request时都会调用该函数。 (用来过滤request)

CrawlSpider如何工作
因为CrawlSpider继承了Spider,所以具有Spider的所有函数

  1. start_requestsstart_urls中的每一个url发起请求(make_requests_from_url),这个请求会**`被parse接收

  2. 在Spider里面的parse需要我们定义,但CrawlSpider定义parse去解析响应(self._parse_response(response, self.parse_start_url, cb_kwargs={}, follow=True))

  3. _parse_response根据有无callback,follow和self.follow_links执行不同的操作

  4. 其中_requests_to_follow又会获取link_extractor(这个是传入的LinkExtractor)解析页面得到的link(link_extractor.extract_links(response)),对url进行加工(process_links,需要自定义),对符合的link发起Request。使用.process_request(需要自定义)处理响应

CrawlSpider如何获取rules
CrawlSpider类会在__init__方法中调用_compile_rules方法,然后在其中浅拷贝rules中的各个Rule获取要用于回调(callback),要进行处理的链接(process_links)和要进行的处理请求(process_request)

链接提取器(Link Extractors)

什么是Link Extractors
Link Extractors 是那些目的仅仅是从网页(scrapy.http.Response 对象)中抽取最终将会被follow链接的对象,Link Extractors 的目的很简单:提取链接

每个link extractor有唯一的公共方法是extract_links ,它接收一个 Response对象,并返回一个 scrapy.link.Link 对象

Link Extractors,要实例化一次并且 extract_links 方法会根据不同的response调用多次提取链接

内置Link Extractor 参考
Scrapy提供的Link Extractor类在 scrapy.linkextractors 模块提供。 默认的link extractor是 LinkExtractor , 其实就是 LxmlLinkExtractor:

from scrapy.contrib.linkextractors import LinkExtractor

主要参数为:

  • allow:满足括号中正则表达式的值会被提取,如果为空,则全部匹配

  • deny:与这个正则表达式(或正则表达式列表)不匹配的URL一定不提取

  • allow_domains:会被提取的链接的domains

  • deny_domains:一定不会被提取链接的domains

  • restrict_xpaths:使用xpath表达式,和allow共同作用过滤链接。还有一个类似的restrict_css

实战

京东商品爬取难点在于全站信息,爬取全站所有信息的最好的方法就是使用通用爬虫CrawlSpider

爬取步骤

  1. 浏览京东网站,寻找商品信息接口

  2. 使用LinkExtractor抓取所有链接,用allow_domains限制爬取信息接口

  3. 使用正则,抓取商品id,拼接商品详情链接

  4. 抓取商品详情以及价格

  5. 尝试分布式爬取

代码实现

  1. 使用命令,建立scrapy文件以及CrawlSpider文件

    >>>scrapy startproject jd
    
    >>>cd jd
    
    >>>scrapy genspider -t crawl jd_spider ‘m.jd.com’
    
  2. 编写jd_spider.py文件,该文件主要负责爬取链接以及商品详情信息

    # -*- coding: utf-8 -*-
    import scrapy
    from scrapy.spiders import CrawlSpider,Rule
    from scrapy.linkextractors import LinkExtractor
    from scrapy.http import Request
    import json
    import re
    import pprint
    
    class JdSpiderSpider(CrawlSpider):
        name = 'jd_spider'    #老朋友,爬虫名字,必不可少
        allowed_domains = ['jd.com','p.3.cn']
        start_urls = ['http://m.jd.com/'] 
    
        rules = [
            Rule(LinkExtractor(allow = ()),      #允许抓取所有链接
                 callback = 'parse_shop',        #回调parse_shop函数
                 follow = True                   #跟随链接
                 ),
            ]
    
        def parse_shop(self, response):
            pass
            ware_id_list = list()
            url_group_shop = LinkExtractor(allow = (r'(http|https)://item.m.jd.com/product/\d+.html')).extract_links(response)    #对链接使用正则表达式进行筛选
    
            re_get_id = re.compile(r'(http|https)://item.m.jd.com/product/(\d+).html')    #定义抓取正则表达式的抓取规则
    
            for url in url_group_shop:
                ware_id = re_get_id.search(url.url).group(2)    #抓取出商品id
                ware_id_list.append(ware_id)                    #商品id添加进ware_id_list
    
            for id in ware_id_list:
                """
                https://item.m.jd.com/ware/detail.json?wareId={}
                https://p.3.cn/prices/mgets?type=1&skuIds=J_{}
                """
    
                yield Request('https://item.m.jd.com/ware/detail.json?wareId={}'.format(id), #拼接详情页面的url
                              callback = self.detail_pag,
                              meta = {'id':id},
                              priority=5
                              )                         
    
        def detail_pag(self,response):
            data = json.loads(response.text)
    
            yield Request('https://p.3.cn/prices/mgets?type=1&skuIds=J_{}'.format(response.meta['id']),          #拼接价格页面的url
                          callback=self.get_price_pag,
                          meta={'id': response.meta['id'],    #把id和data传给下一个函数
                                'data':data
                                },
                          priority = 10
                          )
    
        def get_price_pag(self,response):
            data = json.loads(response.text)
            detail_data = response.meta['data']
            ware_id = response.meta['id']
            item = {
                'detail':detail_data,
                'price':data,
                'ware_id':ware_id
                }
    
            pprint.pprint(item)
            yield item           #转给管道
    
  3. 编写pipeline.py,该文件主要负责入库

    from pymongo import MongoClient
    from scrapy.conf import settings
    from pymongo.errors import DuplicateKeyError
    from traceback import format_exc
    
    class JdPipeline(object):
        def __init__(self, mongo_uri, mongo_db):
            self.mongo_uri = mongo_uri
            self.mongo_db = mongo_db
            self.client = None
            self.db = None
    
        @classmethod
        def from_crawler(cls, crawler):
            return cls(
                mongo_uri=crawler.settings.get('MONGODB_URI'),  # 提取出了mongodb配置
                mongo_db=settings.get('MONGODB_DATABASE', 'items')
            )
    
        def open_spider(self, spider):
            _ = spider
            self.client = MongoClient(self.mongo_uri)  # 连接数据库
            self.db = self.client[self.mongo_db]
            self.db['jd_info'].ensure_index('ware_id', unique=True)  # 在表jd_info中建立索引,并保证索引的唯一性
    
        def close_spider(self, spider):
            _ = spider
            self.client.close()  # 关闭数据库
    
        def process_item(self, item, spider):
            try:
                self.db['jd_info'].update({'ware_id': item['ware_id']}, {'$set': item}, upsert=True)  # 通过id判断,有就更新,没有就插入
    
            except DuplicateKeyError:
                spider.logger.debug(' duplicate key error collection')  # 唯一键冲突报错
            except Exception as e:
                _ = e
                spider.logger.error(format_exc())
            return item
    
  4. 编写settings.py,该文件主要负责激活管道以及设置下载时间

    # -*- coding: utf-8 -*-
    
    # Scrapy settings for jd project
    #
    # For simplicity, this file contains only settings considered important or
    # commonly used. You can find more settings consulting the documentation:
    #
    #     http://doc.scrapy.org/en/latest/topics/settings.html
    #     http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
    #     http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html
    
    BOT_NAME = 'jd'
    
    SPIDER_MODULES = ['jd.spiders']
    NEWSPIDER_MODULE = 'jd.spiders'
    
    
    # Crawl responsibly by identifying yourself (and your website) on the user-agent
    #USER_AGENT = 'jd (+http://www.yourdomain.com)'
    
    # Obey robots.txt rules
    ROBOTSTXT_OBEY = False
    
    # Configure maximum concurrent requests performed by Scrapy (default: 16)
    #CONCURRENT_REQUESTS = 32
    
    # Configure a delay for requests for the same website (default: 0)
    # See http://scrapy.readthedocs.org/en/latest/topics/settings.html#download-delay
    # See also autothrottle settings and docs
    DOWNLOAD_DELAY = 5     #下载时间为5秒一次
    # The download delay setting will honor only one of:
    #CONCURRENT_REQUESTS_PER_DOMAIN = 16
    #CONCURRENT_REQUESTS_PER_IP = 16
    
    # Disable cookies (enabled by default)
    #COOKIES_ENABLED = False
    
    # Disable Telnet Console (enabled by default)
    #TELNETCONSOLE_ENABLED = False
    
    # Override the default request headers:
    #DEFAULT_REQUEST_HEADERS = {
    #   'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    #   'Accept-Language': 'en',
    #}
    
    # Enable or disable spider middlewares
    # See http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html
    #SPIDER_MIDDLEWARES = {
    #    'jd.middlewares.JdSpiderMiddleware': 543,
    #}
    
    # Enable or disable downloader middlewares
    # See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
    #DOWNLOADER_MIDDLEWARES = {
    #    'jd.middlewares.MyCustomDownloaderMiddleware': 543,
    #}
    
    # Enable or disable extensions
    # See http://scrapy.readthedocs.org/en/latest/topics/extensions.html
    #EXTENSIONS = {
    #    'scrapy.extensions.telnet.TelnetConsole': None,
    #}
    
    # Configure item pipelines
    # See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html
    ITEM_PIPELINES = {
       'jd.pipelines.JdPipeline': 300,       #激活管道
    }
    
    # Enable and configure the AutoThrottle extension (disabled by default)
    # See http://doc.scrapy.org/en/latest/topics/autothrottle.html
    #AUTOTHROTTLE_ENABLED = True
    # The initial download delay
    #AUTOTHROTTLE_START_DELAY = 5
    # The maximum download delay to be set in case of high latencies
    #AUTOTHROTTLE_MAX_DELAY = 60
    # The average number of requests Scrapy should be sending in parallel to
    # each remote server
    #AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
    # Enable showing throttling stats for every response received:
    #AUTOTHROTTLE_DEBUG = False
    
    # Enable and configure HTTP caching (disabled by default)
    # See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
    #HTTPCACHE_ENABLED = True
    #HTTPCACHE_EXPIRATION_SECS = 0
    #HTTPCACHE_DIR = 'httpcache'
    #HTTPCACHE_IGNORE_HTTP_CODES = []
    #HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
    
    import os
    MONGODB_HOST = os.environ.get('MONGODB_HOST','127.0.0.1')   #本地数据库
    MONGODB_PORT = os.environ.get('MONGODB_PORT','27017')    #数据库端口
    MONGODB_URI = 'mongodb://{}:{}'.format(MONGODB_HOST, MONGODB_PORT)
    MONGODB_DATABASE = os.environ.get('MONGODB_DATABASE','jd')  #数据库名字
    
  5. 添加分布式配置,再另外多台电脑上复制相同的一份代码,就可以实现多台同时爬取

    SCHEDULER = "scrapy_redis.scheduler.Scheduler"   #必有项:更改去重对列
    
    # Ensure all spiders share same duplicates filter through redis.
    DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"   #必有项:利用Redis去重
    
    REDIS_URL = 'redis://xx.xx.xx.xx:xxxx'   #配置连接
    
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赈川

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值