Python中Scrapy框架

12 篇文章 9 订阅

Scrapy 框架

一、 简介

1、 介绍

Scrapy 是一个基于 Twisted 实现的异步处理爬虫框架,该框架使用纯 Python 语言编写。Scrapy 框架应用广泛,常用于数据采集、网络监测,以及自动化测试等

2、 环境配置

  1. 安装 pywin32
    • pip install pywin32
  2. 安装 wheel
    • pip install wheel
  3. 安装 twisted
    • pip install twisted
  4. 安装 scrapy 框架
    • pip install scrapy

3、 常用命令

命令格式说明
startprojectscrapy startproject <项目名>创建一个新项目
genspiderscrapy genspider <爬虫文件名> <域名>新建爬虫文件
runspiderscrapy runspider <爬虫文件>运行一个爬虫文件,不需要创建项目
crawlscrapy crawl <spidername>运行一个爬虫项目,必须要创建项目
listscrapy list列出项目中所有爬虫文件
viewscrapy view <url地址>从浏览器中打开 url 地址
shellscrapy shell <url地址>命令行交互模式
settingsscrapy settings查看当前项目的配置信息

4、 运行原理

4.1 流程图
4.2 部件简介
  1. 引擎(Engine)

    引擎负责控制系统所有组件之间的数据流,并在某些动作发生时触发事件。

  2. 调度器(Scheduler)

    用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL的优先级队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址

  3. 下载器(Downloader)

    用于下载网页内容, 并将网页内容返回给EGINE,下载器是建立在twisted这个高效的异步模型上的

  4. 爬虫(Spiders)

    是开发人员自定义的类,它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎,再次进入Scheduler(调度器)

  5. 项目管道(Item Pipeline)

    在items被提取后负责处理它们,主要包括清理、验证、持久化(比如存到数据库)等操作

  6. 下载中间件(Downloader Middlerwares)

    你可以当作是一个可以自定义扩展下载功能的组件。

  7. 爬虫中间件(Spider Middlerwares)

    位于EGINE和SPIDERS之间,主要工作是处理SPIDERS的输入(即responses)和输出(即requests)

4.3 运行流程
  1. 引擎:Hi!Spider, 你要处理哪一个网站?
  2. Spider:老大要我处理xxxx.com。
  3. 引擎:你把第一个需要处理的URL给我吧。
  4. Spider:给你,第一个URL是xxxxxxx.com。
  5. 引擎:Hi!调度器,我这有request请求你帮我排序入队一下。
  6. 调度器:好的,正在处理你等一下。
  7. 引擎:Hi!调度器,把你处理好的request请求给我。
  8. 调度器:给你,这是我处理好的request
  9. 引擎:Hi!下载器,你按照老大的下载中间件的设置帮我下载一下这个request请求
  10. 下载器:好的!给你,这是下载好的东西。(如果失败:sorry,这个request下载失败了。然后引擎告诉调度器,这个request下载失败了,你记录一下,我们待会儿再下载)
  11. 引擎:Hi!Spider,这是下载好的东西,并且已经按照老大的下载中间件处理过了,你自己处理一下(注意!这儿responses默认是交给def parse()这个函数处理的)
  12. Spider:(处理完毕数据之后对于需要跟进的URL),Hi!引擎,我这里有两个结果,这个是我需要跟进的URL,还有这个是我获取到的Item数据。
  13. 引擎:Hi !管道 我这儿有个item你帮我处理一下!调度器!这是需要跟进URL你帮我处理下。然后从第四步开始循环,直到获取完老大需要全部信息。
  14. 管道调度器:好的,现在就做!

注意:只有当调度器没有request需要处理时,整个程序才会停止。(对于下载失败的URL,Scrapy也会重新下载。)

二、 创建项目

本次示例是爬取豆瓣

1、 修改配置

LOG_LEVEL = "WARNING"  # 设置日志等级
from fake_useragent import UserAgent
USER_AGENT = UserAgent().random  # 设置请求头
ROBOTSTXT_OBEY = False  # 是否遵守 robots 协议,默认为 True
ITEM_PIPELINES = {  # 开启管道
    'myFirstSpider.pipelines.MyfirstspiderPipeline': 300,  # 300 为权重,
    'myFirstSpider.pipelines.DoubanPipeline': 301,  # 数字越大权重越小
}

2、 创建一个项目

在命令行输入:

(scrapy_) D:\programme\Python\scrapy_>scrapy startproject myFirstSpider

(scrapy_) D:\programme\Python\scrapy_>cd myFirstSpider

(scrapy_) D:\programme\Python\scrapy_\myFirstSpider>scrapy genspider douban "douban.com"

3、 定义数据

定义一个提取的结构化数据(Item)

  1. 打开 myFirstSpider 目录下的 items.py
  2. item 定义结构化的数据字段,用来存储爬取到的数据,有点像python里面的字典,但是提供了一些而外的保护减少错误
  3. 可以通过创建一个 scrapy.Item 类,并且定义类型为 scrapy.Field 的类属性来定义一个 Item (可以理解成类似于ORM的映射关系)
  4. 接下来,创建一个 Douban 类,和构建 item 模型
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html

import scrapy


class MyfirstspiderItem(scrapy.Item):  # 可以自己创建一个类,但是要继承 scrapy.Item 类
    # define the fields for your item here like:
    # name = scrapy.Field()
    pass


class DoubanItem(scrapy.Item):
    title = scrapy.Field()  # 标题
    introduce = scrapy.Field()  # 介绍

4、 编写并提取数据

编写爬取网站的 Spider 并提取出结构化数据(Item)

在定义的爬虫文件中写入:

import scrapy
from ..items import DoubanItem  # 导入定义的格式化数据


class DoubanSpider(scrapy.Spider):
    name = 'douban'  # 爬虫的识别名称,唯一的
    # allowed_domains = ['douban.com']  # 允许爬取的范围
    # start_urls = ['http://douban.com/']  # 最初爬取的 url
    start_urls = ['https://movie.douban.com/top250']  # 可以自己定义要爬取的 url

    def parse(self, response):
        info = response.xpath('//div[@class="info"]')
        for i in info:
            # 存放电影信息合集
            item = DoubanItem()
            title = i.xpath("./div[1]/a/span[1]/text()").extract_first()  # 获取第一个内容,通过extract方法提取selector对象
            introduce = i.xpath("./div[2]/p[1]//text()").extract()  # 获取全部内容
            introduce = "".join(j.strip() for j in [i.replace("\\xa0", '') for i in introduce])  # 整理信息
            item["title"] = title
            item["introduce"] = introduce

            # 将获取的数据交给 pipeline
            yield item

5、 存储数据

编写 Item Pipelines 来存储提取到的 Item (即结构化数据)

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html


# useful for handling different item types with a single interface
from itemadapter import ItemAdapter


class MyfirstspiderPipeline:
    def process_item(self, item, spider):
        return item


class DoubanPipeline:
    # 在爬虫文件开始时,运行此函数
    def open_spider(self, spider):
        if spider.name == "douban":  # 如果数据是从豆瓣爬虫传进来的
            print("爬虫开始运行!")
            self.fp = open("./douban.txt", "w", encoding="utf-8")

    def process_item(self, item, spider):
        if spider.name == "douban":
            self.fp.write(f"标题:{item['title']}, 信息:{item['introduce']}")  # 保存文件

    # 爬虫结束的时候运行
    def close_spider(self, spider):
        if spider.name == "douban":
            print("爬虫结束运行!")
            self.fp.close()

6、 运行文件

(scrapy_) D:\programme\Python\scrapy_\myFirstSpider>scrapy crawl douban 

三、 日志打印

1、 日志信息

日志信息等级:

  • ERROR:错误信息
  • WARNING:警告
  • INFO:一般的信息
  • DEBUG:调试信息

设置日志信息的制定输出

LOG_LEVEL = "ERROR"  # 指定日志信息种类
LOG_FILE = "log.txt"  # 表示将日志信息写到指定的文件中进行存储

2、 logging 模块

imoprt logging
logger = logging.getLogger(__name__)  # __name__ 获得项目的文件名
logger.warning(" info ")  # 打印要输出的日志信息

四、 全站爬取

1、 使用request排序入队

yield scrapy.Request(url=new_url, callback=self.parse_taoche, meta={"page": page})

参数:

  • url:传递的地址
  • callback:请求后响应数据的处理函数
  • meta:传递数据
    • 每次请求都会携带meta参数
    • 传递给响应
    • 可以通过response.meta \ response.meta["page"]获取
import scrapy, logging
from ..items import DetailItem

logger = logging.Logger(__name__)


class DoubanSpider(scrapy.Spider):
    name = 'douban'
    # allowed_domains = ['douban.com']
    start_urls = ['https://movie.douban.com/top250']

    def parse(self, response):
        print(response)
        info = response.xpath('//div[@class="info"]')
        for i in info:
            item_detail = DetailItem()  # 详情页的内容
            # 存放电影信息合集
            title = i.xpath("./div[1]/a/span[1]/text()").extract_first()  # 获取第一个内容,通过extract方法提取selector对象
            item_detail["title"] = title
            logger.warning(title)

            detail_url = i.xpath("./div[1]/a/@href").extract_first()  # 获取详情页的url
            # print(detail_url)
            yield scrapy.Request(url=detail_url, callback=self.parse_detail, meta=item_detail)  # 将请求传递给调度器,重新请求

        next_url = response.xpath("//div[@class='paginator']/span[3]/a/@href").extract_first()  # 获取下一页的url
        if next_url:
            next_url = "https://movie.douban.com/top250" + next_url
            # print(next_url)
            yield scrapy.Request(url=next_url, callback=self.parse, )  # 将请求传递给调度器,重新请求

    def parse_detail(self, resp):
        item = resp.meta  # 接收结构化数据

        introduce = resp.xpath("//div[@id='link-report']/span[1]/span//text()").extract()  # 获取介绍
        item["introduce"] = introduce
        logger.warning(introduce)

        content = resp.xpath("//div[@id='hot-comments']/div[1]//text()").extract()  # 获取评论
        item["content"] = content
        logger.warning(content)

        yield item

2、 继承crawlspider

Scrapy框架中分为两类爬虫:

  • Spider
  • CrawlSpider:
    • CrawlSpider是Spider的派生类,Spider类的设计原理是指爬取start_url列表中的网页,而CrwalSpider类定义了一些规则来提供跟进链接的方便的机制,从爬取的网页中获取链接并继续爬取的工作更合理

创建方法:

scrapy genspider -t crawl 项目名称 网站

创建后,其显示为

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


class FuhaoSpider(CrawlSpider):
    name = 'fuhao'
    # allowed_domains = ['fuhao.com']
    start_urls = ['https://www.phb123.com/renwu/fuhao/shishi_1.html']

    rules = (
        Rule(
            LinkExtractor(allow=r'shishi_\d+.html'),  # 链接提取器,根据正则规则提取url
            callback='parse_item',  # 指定回调函数
            follow=True  # 获取的响应页面是否再次经过rules来进行提取url地址
        ),
    )
    

    def parse_item(self, response):
        print(response.request.url)

Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True)

  • LinkExtractor:链接提取器,根据正则规则提取url地址
  • callback:提取出来的url地址发送请求获取响应,会把响应对象给callback指定的函数进行处理
  • follow:获取的响应页面是否再次经过rules来进行提取url地址
# 匹配豆瓣
start_urls = ['https://movie.douban.com/top250?start=0&filter=']
rules = (
    Rule(LinkExtractor(allow=r'?start=\d+&filter='), callback='parse_item', follow=True),
)

五、 二进制文件

1、 图片下载

ImagesPipeLine:图片下载的模块

pipeline中,编写代码(已知,item里面传输的是图片的下载地址)

import logging
import scrapy
from itemadapter import ItemAdapter
from scrapy.pipelines.images import ImagesPipeline


# 继承ImagesPipeLine
class PicPipeLine(ImagesPipeline):
    # 根据图片地址,发起请求
    def get_media_requests(self, item, info):
        src = item["src"]  # item["src] 里面存储的是图片的地址
        logging.warning("正在访问图片:", src)
        yield scrapy.Request(url = src,meta={'item':item})  # 对图片发起请求

    # 指定图片的名字
    def file_path(self, request, response=None, info=None, *, item=None):
        item = request.meta['item']  # 接收meta参数
        return request.url.split("/")[-1]  # 设置文件名字
        # 在settings中设置 IMAGES_STORE = "./imags"  # 设置图片保存的文件夹

    # 返回数据给下一个即将被执行的管道类
    def item_completed(self, results, item, info):
        return item  

六、 middlewares

1、下载中间件

更换代理IP,更换Cookies,更换User-Agent,自动重试

在settings.py 中添加

# 建立ip池
PROXY_LIST = []

在middlewares.py中添加

from fake_useragent import UserAgent
import random
class Spider4DownloaderMiddleware:
    # 拦截所有请求
    def process_request(self, request, spider):
        # UA 伪装
        request.headers["User-Agent"] = UserAgent().random
        return None

    # 处理请求,可以篡改响应信息
    def process_response(self, request, response, spider):
        bro = spider.bro
        if request.url in spider.model_urls:
            # print(request.url)
            # 要篡改request请求的响应对象, response

            bro.get(request.url)

            # 执行js代码
            bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
            # 一拉到底,发现我们滚动条还是在中间位置

            bottom = []  # 空列表,表示没有到底部
            while not bottom:  # bool([]) ==> false not false
                bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')

                page_text = bro.page_source  # 获取页面内容
                # 如果到底,循环结束
                bottom = re.findall(r'<div class="load_more_tip" style="display: block;">:-\)已经到最后啦~</div>', page_text)
                time.sleep(1)

                if not bottom:
                    try:
                        bro.find_element(By.CSS_SELECTOR, '.load_more_btn').click()  # 找到加载更多进行点击
                    except:
                        bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
                        
            return HtmlResponse(url=request.url, body=page_text, encoding='utf-8', request=request)
        return response

    # 处理异常,当网络请求失败时,执行此函数
    def process_exception(self, request, exception, spider):
        # 添加代理ip
        type_ = request.url.split(":")[0]
        request.meta['proxy'] = f"{type_}://{random.choice(spider.settings.get('PROXY_LIST'))}"
        return request  # 如果ip被封了,就使用代理ip,重新发送请求
	
    # 开始爬虫时执行
    def spider_opened(self, spider):
        spider.logger.info('Spider opened: %s' % spider.name)

设置完下载中间件后,要在settings配置文件中开启

2、 爬虫中间件

爬虫中间件的用法与下载器中间件非常相似,只是它们的作用对象不同。下载器中间件的作用对象是请求request和返回response;爬虫中间件的作用对象是爬虫,更具体地来说,就是写在spiders文件夹下面的各个文件

  1. 当运行到yield scrapy.Request()或者yield item的时候,爬虫中间件的process_spider_output()方法被调用
  2. 当爬虫本身的代码出现了Exception的时候,爬虫中间件的process_spider_exception()方法被调用
  3. 当爬虫里面的某一个回调函数parse_xxx()被调用之前,爬虫中间件的process_spider_input()方法被调用
  4. 当运行到start_requests()的时候,爬虫中间件的process_start_requests()方法被调用
import scrapy


class Spider5SpiderMiddleware:
    # 在下载器中间件处理完成后,马上要进入某个回调函数parse_xxx()前调用
    def process_spider_input(self, response, spider):
        return None

    # 在爬虫运行yield item或者yield scrapy.Request()的时候调用
    def process_spider_output(self, response, result, spider):
        for item in result:
            print(result)
            if isinstance(item, scrapy.Item):
                # 这里可以对即将被提交给pipeline的item进行各种操作
                print(f'item将会被提交给pipeline')
            yield item  # 也可以 yield request,当为yield request时,可以修改请求信息,如meta等

    # 当在爬虫程序运行过程中报错时调用
    def process_spider_exception(self, response, exception, spider):
        """
        爬虫里面如果发现了参数错误,就使用raise这个关键字人工抛出一个自定义的异常。在实际爬虫开发中,可以在某些地方故意不使用try ...
        except捕获异常,而是让异常直接抛出。例如XPath匹配处理的结果,直接读里面的值,不用先判断列表是否为空。这样如果列表为空,就会被抛出一个IndexError,
        于是就能让爬虫的流程进入到爬虫中间件的process_spider_exception()中
        """
        print("第%s页出现错误,错误信息:%s" % response.meta["page"], exception)  # 这里可以捕获异常信息,也可以有返回值

    # 当爬虫运行到start_request时被调用
    def process_start_requests(self, start_requests, spider):
        for r in start_requests:
            print(r.text)
            yield r

    # 当爬虫开始时调用
    def spider_opened(self, spider):
        spider.logger.info('Spider opened: %s' % spider.name)

注意:要在settings配置文件中开启爬虫中间件

七、 模拟登录

1、 cookie

在整个框架运作前,需要一个启动条件,这个启动条件就是start_urls,首先从start_urls的网页发起requests请求,才会有后面的调度器、下载器、爬虫、管道的运转。所以,这里我们可以针对start_urls进行网络请求的start_requests方法进行重写,把我们的cookie给携带进去

注意:必须要使用yield返回,不然没办法运行

import scrapy


class ExampleSpider(scrapy.Spider):
    name = 'example'
    # allowed_domains = ['example.com']
    start_urls = ['https://www.baidu.com']

    # 重写start_request方法,scrapy从这里开始
    def start_requests(self):
        # 添加cookie的第一种方法,直接添加
        cookie = " "
        cookie_dic = {}
        for i in cookie.split(";"):
            cookie_dic[i.split("=")[0]] = i.split("=")[1]

        # 添加cookie的第二种方法:添加头部
        headers = {
            "cookie": "cookie_info",
            # 使用headers传入cookie时,要在settings中加入COOKIES_ENABLE = True
        }
        for url in self.start_urls:
            yield scrapy.Request(url=url, callback=self.parse, headers=headers)  # 添加cookies

    def parse(self, response):
        print(response.text)

2、 直接登录

通过传递参数,访问接口,来实现模拟登录:

第一种方法的使用方法:

import scrapy


class ExampleSpider(scrapy.Spider):
    name = 'example'
    # allowed_domains = ['example.com']
    start_urls = ['https://github.com']

    def parse(self, response):
        # 这里面填写大量的登录参数
        post_data = {
            "username": "lzk",
            "password": "123456",
            "time": "123",
            "sad": "asdsad12",
        }
        # 把登录参数传入服务器,验证登录
        # 方法一
        yield scrapy.FormRequest(
            url='https://github.com/session',
            formdata=post_data,
            callback=self.parse_login,
        )

    def parse_login(self, response):
        print(response.text)

第二种方法的使用方法

# -*- coding: utf-8 -*-
import scrapy
from scrapy import FormRequest, Request


class ExampleLoginSpider(scrapy.Spider):
    name = "login_"
    # allowed_domains = ["example.webscraping.com"]
    start_urls = ['http://example.webscraping.com/user/profile']
    login_url = 'http://example.webscraping.com/places/default/user/login'

        def start_requests(self):
        # 重写start_requests方法,用来登录
        yield scrapy.Request(
            self.login_url,
            callback=self.login
        )

    def login(self,response):
        formdata = {
      	 	'email': 'liushuo@webscraping.com',
            'password': '12345678'
        	}
        yield FormRequest.from_response(
            response, 
            formdata=formdata, 
            callback=self.parse_login
        )
        
    def parse_login(self, response):
        if 'Welcome Liu' in response.text:
            yield from super().start_requests()  # 继承start_requests 的作用,访问要访问的页面
            
    def parse(self, response):
        print(response.text)

使用from_response方法发送请求,等同于selenium里面的查找表单直接将数据填入表单中,不用考虑加密

八、 分布式爬虫

1、概念

概念:

  • 多台机器对一个项目进行分布联合爬取

作用:

  • 增加工作单位,提升爬取效率

实现:

  • 多台机器共用一个调度器
    • 实现一个公有调度器
      • 首先要保证每台机器都可以进行连接,其次的话要能够进行存储,也就是存储我们爬取的url,就是数据库的存储功能,使用redis
        • 可以把url由爬虫交给引擎,引擎给redis
        • 也可以把url由调度器交给redis
        • 同样也可以在持久化存储中,也由管道把item数据交给redis进行存储
      • 安装
        • pip install scrapy-redis -i https://pypi.com/simple

2、 用法

在settings配置文件中添加

# 使用scrapy_redis的管道,其为定义好的管道,直接调用就可以
ITEM_PIPELINES = {
   'scrapy_redis.pipelines.RedisPipeline': 300,
}
# 指定redis地址
REDIS_HOST = '192.168.45.132'  # redis服务器地址,我们使用的虚拟机
REDIS_PORT = 6379  # redis端口

# 使用scrapy_redis 的调度器
SCHEDULER = 'scrapy_redis.scheduler.Scheduler'

# 去重容器类配置,作用:redis的set集合,来存储请求的指纹数据,从而实现去重的持久化
DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter'

# 配置调度器是否需要持久化,爬虫结束的时候要不要清空Redis中请求队列和指纹的set集合,要持久化设置为True
SCHEDULER_PERSIST = True

在爬虫文件中添加

import scrapy
from ..items import TaoCheItem
from scrapy_redis.spiders import RedisCrawlSpider
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule


# 注意 如果使用的是scrapy.Spider 那么使用redis分布式的时候,就继承 RedisSpider
# 如果是CrawlSpider 就继承 RedisCrawlSpider
class TaocheSpider(RedisCrawlSpider):
    name = 'taoche'
    # allowed_domains = ['taoche.com']
    # start_urls = ['https://changsha.taoche.com/bmw/?page=1']  # 起始的url应该去redis(公共调度器)里面获取

    redis_key = 'taoche'  # 回去redis里面获取key值为taoche的数据
    rules = (
        Rule(LinkExtractor(allow=r'/\?page=\d+'), callback='parse_item', follow=True),
    )

    def parse_item(self, response):
        car_list = response.xpath('//div[@id="container_base"]/ul/li')
        for car in car_list:
            lazyimg = car.xpath('./div[1]/div/a/img/@src').extract_first()
            lazyimg = 'https:' + lazyimg
            title = car.xpath('./div[2]/a/span/text()').extract_first()
            resisted_date = car.xpath('./div[2]/p/i[1]/text()').extract_first()
            mileage = car.xpath('./div[2]/p/i[2]/text()').extract_first()
            city = car.xpath('./div[2]/p/i[3]/text()').extract_first().replace('\n', '').strip()
            price = car.xpath('./div[2]/div[1]/i[1]//text()').extract()
            price = ''.join(price)
            sail_price = car.xpath('./div[2]/div[1]/i[2]/text()').extract_first()
            print(lazyimg, title, resisted_date, mileage, city, price, sail_price)
  • 14
    点赞
  • 55
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

SteveKenny

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

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

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

打赏作者

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

抵扣说明:

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

余额充值