scrapy框架使用总结

scrapy总结

1 scrapy项目开发流程

1.1 创建项目命令

scrapy startproject guokespider

1.2 创建一个爬虫

在终端中,先进入到爬虫项目目录下, 然后执行生成爬虫命令,格式scrapy genspider 爬虫名 域名

scrapy genspider guoke www.guokr.com

1.3 数据建模

items.py文件中进行建模

1.4 编写爬虫

  • 修改start_urls
  • 检查修改allowed_domains
  • 编写爬虫解析响应方法
  • pipelines.py文件中,创建处理数据的管道,用于保存数据
  • 修改settings.py配置文件, 注册管道

1.5 运行爬虫

scrapy crawl guoke --nolog

下面来简单介绍一下各个主要文件的作用:

scrapy.cfg :项目的配置文件

mySpider/ :项目的Python模块,将会从这里引用代码

mySpider/items.py :项目的目标文件

mySpider/pipelines.py :项目的管道文件

mySpider/settings.py :项目的设置文件

mySpider/spiders/ :存储爬虫代码目录

2 scrapy的运行流程

  • Scrapy Engine(引擎): 负责SpiderItemPipelineDownloaderScheduler中间的通讯,信号、数据传递等。
  • Scheduler(调度器): 存放request对象,把request对象>引擎>下载器中间件==>下载器,并按照一定的方式进行整理排列,入队,当引擎需要时,交还给引擎
  • Downloader(下载器):负责下载Scrapy Engine(引擎)发送的所有Requests请求,并将其获取到的Responses交还给Scrapy Engine(引擎),由引擎交给Spider来处理,生成response对象>下载器中间件>引擎>爬虫中间件>爬虫
  • Spider(爬虫):提取url和数据,提取的url会转换成request对象>爬虫中间件>引擎>调度器,而提取的数据通过引擎交给管道,即数据>爬虫中间件>引擎>管道
  • Item Pipeline(管道):数据处理,保存数据。它负责处理Spider中获取到的Item,并进行进行后期处理(详细分析、过滤、存储等)的地方.
  • Downloader Middlewares(下载中间件):你可以当作是一个可以自定义扩展下载功能的组件。
  • Spider Middlewares(Spider中间件):你可以理解为是一个可以自定扩展和操作引擎Spider中间通信的功能组件(比如进入Spider的Responses;和从Spider出去的Requests)

3 代码案例

3.1 新建scrapy项目

  • 创建爬虫项目,命令:scrapy startproject 项目名称
  • 创建爬虫文件,命令:scrapy genspider 文件名称 域名

3.2 构建项目模型

import scrapy


class BookItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    book_cate01 = scrapy.Field()
    book_cate02 = scrapy.Field()
    book_cate03 = scrapy.Field()
    book_href = scrapy.Field()
    book_name = scrapy.Field()
    book_store = scrapy.Field()
    book_num = scrapy.Field()

构建的字段模型 可以像 python字典一样使用

在爬虫文件中suning.py导入定义的字段项目模型:

import scrapy
from book.items import BookItem

然后再解析方法parse中实例项目模型即可当做字典使用:

def parse(self, response, **kwargs):
    """解析响应中的数据
        :param **kwargs:
        """
    # 一级分类列表
    div_list = response.xpath('//div[@class="menu-item"]')[:7]
    # 二级分类列表
    div_sub_list = response.xpath('//div[@class="menu-list"]/div[@class="menu-sub"]')
    print(div_sub_list)

    for div in div_list[:1]:
        item = BookItem()
        item['book_cate01'] = div.xpath('.//h3/a/text()').extract_first()
        pass

3.3 启用管道

class SomethingPipeline(object):
    def __init__(self):
        # 可选实现,做参数初始化等
        # doing something

    def process_item(self, item, spider):
        # item (Item 对象) – 被爬取的item
        # spider (Spider 对象) – 爬取该item的spider
        # 这个方法必须实现,每个item pipeline组件都需要调用该方法,
        # 这个方法必须返回一个 Item 对象,被丢弃的item将不会被之后的pipeline组件所处理。
        return item

    def open_spider(self, spider):
        # 在爬虫开始的时候仅执行一次
        # 可选实现,当spider被开启时,这个方法被调用。

    def close_spider(self, spider):
        # 在爬虫结束的时候仅执行一次
        # 可选实现,当spider被关闭时,这个方法被调用
  • 启用一个Item Pipeline组件 为了启用Item Pipeline组件,需要在 settings.py文件中ITEM_PIPELINES 注册:

    # 分配给每个类的整型值,确定了他们运行的顺序,item按数字从低到高的顺序,通过pipeline,通常将这些数字定
    ITEM_PIPELINES = {
       'book.pipelines.BookPipeline': 300,
    }
    

3.4 数据持久化

mongodb为例,将数据存入mongodb:

import pymongo


class BookPipeline(object):
    def open_spider(self, spider):
        """
        爬虫开始只执行一次
        :param spider: 
        :return: 
        """
        # 创建mongodb的数据库连接
        self.client = pymongo.MongoClient(host='192.168.99.100', port=27018)
        # 选择数据库和数据表
        self.cursor = self.client['suning']['book']
        # 清空数据
        self.cursor.delete_many({})

    def process_item(self, item, spider):
        # 保存到数据库中
        self.cursor.insert_one(dict(item))
        return item

    def close_spider(self, spider):
        """
        爬虫结束时只执行一次
        :param spider: 
        :return: 
        """
        # 关闭连接
        self.client.close()
        print('爬虫结束')

3.5 数据解析

# -*- coding: utf-8 -*-
from copy import deepcopy
from pprint import pprint

import scrapy
from book.items import BookItem


class SuningSpider(scrapy.Spider):
    name = 'suning'
    allowed_domains = ['suning.com']
    start_urls = ['https://book.suning.com/']
    # 当前页的书籍计数
    book_num = 0

    def parse(self, response, **kwargs):
        """解析响应中的数据
        :param **kwargs:
        """
        # 一级分类列表
        div_list = response.xpath('//div[@class="menu-item"]')[:7]
        # 二级分类列表
        div_sub_list = response.xpath('//div[@class="menu-list"]/div[@class="menu-sub"]')
        print(div_sub_list)

        for div in div_list[:1]:
            item = BookItem()
            item['book_cate01'] = div.xpath('.//h3/a/text()').extract_first()
            # 一级分类下的元素列表
            sub_div = div_sub_list[div_list.index(div)]
            # 一级分类的所有二级分类和三级分类
            p_list = sub_div.xpath('./div[@class="submenu-left"]/p')

            for p in p_list[:1]:
                # 二级分类
                item['book_cate02'] = p.xpath('./a/text()').extract_first()
                # 三级分类元素列表
                li_list = p.xpath('./following-sibling::ul[1]/li')
                for li in li_list[:1]:
                    item['book_cate03'] = li.xpath('./a/text()').extract_first()
                    item['book_href'] = li.xpath('./a/@href').extract_first()

                    yield scrapy.Request(
                        item['book_href'],
                        callback=self.parse_book_list,
                        meta={'item': deepcopy(item)}
                    )

                    # 获取当前页的后半部分的数据
                    next_part_url = "https://list.suning.com/emall/showProductList.do?ci={}&pg=03&cp=0&il=0&iy=0&adNumber=0&n=1&ch=4&prune=0&sesab=ACBAAB&id=IDENTIFYING&paging=1&sub=0"
                    ci = item['book_href'].split('-')[1]
                    next_part_url = next_part_url.format(ci)
                    print(next_part_url)
                    yield scrapy.Request(
                        next_part_url,
                        callback=self.parse_book_list,
                        meta={'item': deepcopy(item)}
                    )
                    print('=' * 10)
                    next_part_url.format(ci)

    def parse_book_list(self, response):
        """解析response中的所有book"""
        print(1)
        item = response.meta.get('item')
        li_list = response.xpath('//li[contains(@class,"product      book")]')
        for li in li_list:
            item['book_name'] = li.xpath('.//p[@class="sell-point"]/a/text()').extract_first().strip()
            item['book_href'] = li.xpath('.//p[@class="sell-point"]/a/@href').extract_first()
            item['book_store'] = li.xpath('.//p[contains(@class, "seller oh no-more")]/a/text()').extract_first()

            yield scrapy.Request(
                response.urljoin(item['book_href']),
                callback=self.parse_book_detail,
                meta={'item': deepcopy(item)}
            )

        # TODO 分页

    def parse_book_detail(self, response):
        """解析书籍信息"""
        # get date
        item = response.meta.get('item')
        self.book_num += 1
        item['book_num'] = self.book_num
        yield item

4 CrawlSpider的高阶使用

通过命令创建 CrawlSpider爬虫的代码:

scrapy genspider -t crawl tencent tencent.com

它是Spider的派生类,Spider类的设计原则是只爬取start_url列表中的网页,而CrawlSpider类定义了一些规则(rule)来提供跟进link的方便的机制,从爬取的网页中获取link并继续爬取的工作更适合。

CrawlSpider继承于Spider类,除了继承过来的属性外(name、allow_domains),还提供了新的属性和方法

4.1 rules

CrawlSpider使用rules来决定爬虫的爬取规则,并将匹配后的url请求提交给引擎。所以在正常情况下,CrawlSpider不需要单独手动返回请求了。

在rules中包含一个或多个Rule对象,每个Rule对爬取网站的动作定义了某种特定操作,比如提取当前相应内容里的特定链接,是否对提取的链接跟进爬取,对提交的请求设置回调函数等。

如果多个rule匹配了相同的链接,则根据规则在本集合中被定义的顺序,第一个会被使用。

class scrapy.spiders.Rule(
        link_extractor,
        callback = None,
        cb_kwargs = None,
        follow = None,
        process_links = None,
        process_request = None
)
  • 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)

4.2 LinkExtractors

class scrapy.linkextractors.LinkExtractor

Link Extractors 的目的很简单: 提取链接。

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

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

class scrapy.linkextractors.LinkExtractor(
    allow = (),
    deny = (),
    allow_domains = (),
    deny_domains = (),
    deny_extensions = None,
    restrict_xpaths = (),
    tags = ('a','area'),
    attrs = ('href'),
    canonicalize = True,
    unique = True,
    process_value = None
)

主要参数:

  • allow:满足括号中“正则表达式”的URL会被提取,如果为空,则全部匹配。
  • deny:满足括号中“正则表达式”的URL一定不提取(优先级高于allow)。
  • allow_domains:会被提取的链接的domains。
  • deny_domains:一定不会被提取链接的domains。
  • restrict_xpaths:使用xpath表达式,和allow共同作用过滤链接。

4.3 爬取规则(Crawling rules)

以腾讯招聘为例,给出配合rule使用CrawlSpider的例子:

  1. 首先运行

     scrapy shell "http://hr.tencent.com/position.php?&start=0#a"
    
  2. 导入LinkExtractor,创建LinkExtractor实例对象。:

     from scrapy.linkextractors import LinkExtractor
    
     page_lx = LinkExtractor(allow=('position.php?&start=\d+'))
    

    allow : LinkExtractor对象最重要的参数之一,这是一个正则表达式,必须要匹配这个正则表达式(或正则表达式列表)的URL才会被提取,如果没有给出(或为空), 它会匹配所有的链接。

    deny : 用法同allow,只不过与这个正则表达式匹配的URL不会被提取)。它的优先级高于 allow 的参数,如果没有给出(或None), 将不排除任何链接。

  3. 调用LinkExtractor实例的extract_links()方法查询匹配结果:

     page_lx.extract_links(response)
    
  4. 没有查到:

     []
    
  5. 注意转义字符的问题,继续重新匹配:

     page_lx = LinkExtractor(allow=('position\.php\?&start=\d+'))
     # page_lx = LinkExtractor(allow = ('start=\d+'))
    
     page_lx.extract_links(response)
    

4.4 CrawlSpider 版本

那么,scrapy shell测试完成之后,修改以下代码

#提取匹配 'http://hr.tencent.com/position.php?&start=\d+'的链接
page_lx = LinkExtractor(allow = ('start=\d+'))

rules = [
    #提取匹配,并使用spider的parse方法进行分析;并跟进链接(没有callback意味着follow默认为True)
    Rule(page_lx, callback = 'parse', follow = True)
]

这么写对吗?

不对!千万记住 callback 千万不能写 parse,再次强调:由于CrawlSpider使用parse方法来实现其逻辑,如果覆盖了 parse方法,crawl spider将会运行失败。

import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule


class TecentSpider(CrawlSpider):
    name = 'tecent'
    allowed_domains = ['hr.tencent.com']
    start_urls = ['http://hr.tencent.com/position.php?&start=0']
    page_lx = LinkExtractor(allow=r'start=\d+')
    #position.php?&start=10#a
    rules = (
        Rule(page_lx, callback='parse_item', follow=True),
    )

    def parse_item(self, response):
        items = response.xpath('//*[contains(@class,"odd") or contains(@class,"even")]')
        for item in items:
            temp = dict(
                position=item.xpath("./td[1]/a/text()").extract()[0],
                detailLink="http://hr.tencent.com/" + item.xpath("./td[1]/a/@href").extract()[0],
                type=item.xpath('./td[2]/text()').extract()[0] if len(
                    item.xpath('./td[2]/text()').extract()) > 0 else None,
                need_num=item.xpath('./td[3]/text()').extract()[0],
                location=item.xpath('./td[4]/text()').extract()[0],
                publish_time=item.xpath('./td[5]/text()').extract()[0]
            )
            print(temp)
            yield temp

    # parse() 方法不需要重写     
    # def parse(self, response):                                              
    #     pass

运行: scrapy crawl tencent

5 Logging

Scrapy提供了log功能,可以通过 logging 模块使用。

可以修改配置文件settings.py,任意位置添加下面两行,效果会清爽很多。

LOG_FILE = "TencentSpider.log"
LOG_LEVEL = "INFO"

5.1 Log levels

  • Scrapy提供5层logging级别:
    • CRITICAL- 严重错误(critical)
    • ERROR - 一般错误(regular errors)
    • WARNING - 警告信息(warning messages)
    • INFO - 一般信息(informational messages)
    • DEBUG - 调试信息(debugging messages)

5.2 logging设置

通过在setting.py中进行以下设置可以被用来配置logging:

  1. LOG_ENABLED 默认: True,启用logging
  2. LOG_ENCODING 默认: 'utf-8',logging使用的编码
  3. LOG_FILE 默认: None,在当前目录里创建logging输出文件的文件名
  4. LOG_LEVEL 默认: 'DEBUG',log的最低级别
  5. LOG_STDOUT 默认: False 如果为 True,进程所有的标准输出(及错误)将会被重定向到log中。例如,执行 print "hello" ,其将会在Scrapy log中显示

6 中间件

6.1 分类和作用

  • 分类

    • 下载中间件
    • 爬虫中间件
  • 作用-预处理requestreponse对象

    • headercookie进行更换处理
    • 使用代理ip
    • 对请求进行定制化操作

两种中间件都在middlewares.py文件中

6.2 下载中间件(Downloader Middlewares)

下载中间件是处于引擎(crawler.engine)和下载器(crawler.engine.download())之间的一层组件,可以有多个下载中间件被加载运行。

  1. 当引擎传递请求给下载器的过程中,下载中间件可以对请求进行处理 (例如增加http header信息,增加proxy信息等);
  2. 在下载器完成http请求,传递响应给引擎的过程中, 下载中间件可以对响应进行处理(例如进行gzip的解压等)

要激活下载器中间件组件,将其加入到 DOWNLOADER_MIDDLEWARES 设置中。 该设置是一个字典(dict),键为中间件类的路径,值为其中间件的顺序(order)。

这里是一个例子:

DOWNLOADER_MIDDLEWARES = {
    'mySpider.middlewares.MyDownloaderMiddleware': 543,
}

编写下载器中间件十分简单。每个中间件组件是一个定义了以下一个或多个方法的Python类:

class scrapy.contrib.downloadermiddleware.DownloaderMiddleware

6.2.1 process_request

process_request(self, request, spider)

当每个request通过下载中间件时,该方法被调用。

  • process_request() 必须返回以下其中之一:一个 None 、一个 Response 对象、一个 Request 对象或 raise IgnoreRequest:

    • 返回 None值 :

      Scrapy将继续处理该request,执行其他的中间件的相应方法,直到合适的下载器(download handler)被调用, 该request被执行(其response被下载。

    • 返回 Response 对象:

      Scrapy将不会调用 任何 其他的 process_request() 或 process_exception() 方法,或相应地下载函数; 其将返回该response给引擎。

    • 返回 Request 对象:

      Scrapy则停止调用 process_request方法并通过引擎重新返回给调度器。当新返回的request被执行后, 相应地中间件链将会根据下载的response被调用。

    • 如果其raise一个 IgnoreRequest 异常,则安装的下载中间件的 process_exception() 方法会被调用。如果没有任何一个方法处理该异常, 则request的errback(Request.errback)方法会被调用。如果没有代码处理抛出的异常, 则该异常被忽略且不记录(不同于其他异常那样)。

  • 参数:

    • request (Request 对象) – 处理的request
    • spider (Spider 对象) – 该request对应的spider

6.2.2 process_response

process_response(self, request, response, spider)

当下载器完成http请求,传递响应给引擎的时候调用

  • process_response() 必须返回以下其中之一: 返回一个 Response 对象、 返回一个 Request 对象或raise一个 IgnoreRequest 异常。

    • 返回 Response 对象:

      可以与传入的response相同,也可以是全新的对象, 该response会被在链中的其他中间件的 process_response() 方法处理。

    • 返回 Request 对象:

      中间件停止处理, 返回的request会通过引擎交给调度器,等待重新请求。处理类似于 process_request() 返回request所做的那样。

    • 如果其抛出一个 IgnoreRequest 异常,则调用request的errback(Request.errback)。 如果没有代码处理抛出的异常,则该异常被忽略且不记录(不同于其他异常那样)。

  • 参数:

    • request (Request 对象) – response所对应的request
    • response (Response 对象) – 被处理的response
    • spider (Spider 对象) – response所对应的spider

6.3 使用案例

1. 创建middlewares.py文件。

Scrapy代理IP、Uesr-Agent的切换都是通过DOWNLOADER_MIDDLEWARES进行控制,我们在settings.py同级目录下创建middlewares.py文件,包装所有请求。

# middlewares.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import random
import base64

from settings import USER_AGENTS
from settings import PROXIES

# 随机的User-Agent
class RandomUserAgent(object):
    def process_request(self, request, spider):
        useragent = random.choice(USER_AGENTS)

        request.headers.setdefault("User-Agent", useragent)

class RandomProxy(object):
    def process_request(self, request, spider):
        proxy = random.choice(PROXIES)

        if proxy['user_passwd'] is None:
            # 没有代理账户验证的代理使用方式
            request.meta['proxy'] = "http://" + proxy['ip_port']
        else:
            # 对账户密码进行base64编码转换
            base64_userpasswd = base64.b64encode(proxy['user_passwd'])
            # 对应到代理服务器的信令格式里
            request.headers['Proxy-Authorization'] = 'Basic ' + base64_userpasswd
            request.meta['proxy'] = "http://" + proxy['ip_port']

为什么HTTP代理要使用base64编码:

HTTP代理的原理很简单,就是通过HTTP协议与代理服务器建立连接,协议信令中包含要连接到的远程主机的IP和端口号,如果有需要身份验证的话还需要加上授权信息,服务器收到信令后首先进行身份验证,通过后便与远程主机建立连接,连接成功之后会返回给客户端200,表示验证通过,就这么简单,下面是具体的信令格式:

CONNECT 59.64.128.198:21 HTTP/1.1
Host: 59.64.128.198:21
Proxy-Authorization: Basic bGV2I1TU5OTIz
User-Agent: OpenFetion

其中Proxy-Authorization是身份验证信息,Basic后面的字符串是用户名和密码组合后进行base64编码的结果,也就是对username:password进行base64编码。

HTTP/1.0 200 Connection established

OK,客户端收到收面的信令后表示成功建立连接,接下来要发送给远程主机的数据就可以发送给代理服务器了,代理服务器建立连接后会在根据IP地址和端口号对应的连接放入缓存,收到信令后再根据IP地址和端口号从缓存中找到对应的连接,将数据通过该连接转发出去。

2. 修改settings.py配置USER_AGENTS和PROXIES

  • 添加USER_AGENTS:
  USER_AGENTS = [
    "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
    "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
    "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)",
    "Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0",
    "Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5"
    ]
  • 添加代理IP设置PROXIES:

    免费代理IP可以网上搜索,或者付费购买一批可用的私密代理IP:

PROXIES = [
    {'ip_port': '111.8.60.9:8123', 'user_passwd': 'user1:pass1'},
    {'ip_port': '101.71.27.120:80', 'user_passwd': 'user2:pass2'},
    {'ip_port': '122.96.59.104:80', 'user_passwd': 'user3:pass3'},
    {'ip_port': '122.224.249.122:8088', 'user_passwd': 'user4:pass4'},
]
  • 除非特殊需要,禁用cookies,防止某些网站根据Cookie来封锁爬虫。
COOKIES_ENABLED = False
  • 设置下载延迟
DOWNLOAD_DELAY = 3
  • 最后设置setting.py里的DOWNLOADER_MIDDLEWARES,添加自己编写的下载中间件类。
DOWNLOADER_MIDDLEWARES = {
    #'mySpider.middlewares.MyCustomDownloaderMiddleware': 543,
    'mySpider.middlewares.RandomUserAgent': 1,
    'mySpider.middlewares.ProxyMiddleware': 100
}
posted @   三叶草body  阅读(151)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示