文章目录

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

主要参数为:

  • 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’
  1. 编写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 #转给管道
  1. 编写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
  1. 编写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') #数据库名字
  1. 添加分布式配置,再另外多台电脑上复制相同的一份代码,就可以实现多台同时爬取
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' #配置连接