Scrapy框架

Scrapy官方文档

B站黑马教程

B站尚硅谷教程

代码文件

scrapy框架

  • Scrapy是用纯Python实现一个为了爬取网站数据、提取结构性数据而编写的应用框架,用途非常广泛。
  • 框架的力量,用户只需要定制开发几个模块就可以轻松的实现一个爬虫,用来抓取网页内容以及各种图片,非常之方便。
  • Scrapy 使用了 Twisted['twrstrd](其主要对手是Tornado)异步网络框架来处理网络通讯,可以加快我们的下载速度,不用自己去实现异步框架,并且包含了各种中间件接口,可以灵活的完成各种需求

scrapy是什么?

Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。可以应用在包括数据挖掘,信息处理
或存储历史数据等一系列的程序中。

安装scrapy

安装非Python的依赖 
sudo apt-get install python-dev python-pip libxm12-dev libxslt1-dev zlib1g-dev libffi-dev libssl-dev(Ubuntu下安装)

pip install scrapy 

安装过程中出错:

  1. 如果安装有错误!!!!

    pip install Scrapy
    building 'twisted.test.raiser' extension
    error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ 
    Build Tools": http://landinghub.visualstudio.com/visual‐cpp‐build‐tools
    

解决方案:
http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted

下载twisted对应版本的whl文件(如我的Twisted‐17.5.0‐cp36‐cp36m‐win_amd64.whl),cp后面是python版本,amd64代表64位,运行命令:
pip install C:\Users…\Twisted‐17.5.0‐cp36‐cp36m‐win_amd64.whl

pip install Scrapy

  1. 如果再报错: python ‐m pip install ‐‐upgrade pip
  2. 如果再报错 win32

解决方法:
pip install pypiwin32

  1. 再报错:使用anaconda
    使用步骤:
    • 打开anaconda
    • 点击environments
    • 点击not installed
    • 输入scrapy
    • apply(应用)
    • pycharm中选择anaconda的环境

Scrapy架构图(绿线是数据流向)

在这里插入图片描述

  • Scrapy Engine(引擎):负责SpiderItem PipelineDownloaderScheduler 中间的通讯,信号、数据传递等。
  • Scheduler(调度器):它负责接受引擎发送过来的Request请求,并按照一定的方式进行整理排列,入队,当引擎 需要时,交还给引擎
  • Downloader(下载器):负责下载 Scrapy Engine(引擎)发送的所有Requests请求,并将其获取到的Responses交还给 Scrapy Engine(引擎),由引擎交给 Spider 来处理。
  • Item Pipeline(管道):它负责处理 Spider 中获取到的 Item(有用的数据,需要存储的),并进行进行后期处理(详细分析、过滤、存储等)的地方.
  • Downloader middlewares(下载中间件):你可以当作是一个可以自定义扩展下载功能的组件。
  • Spider Middlewares(spider中间件):你可以理解为是一个可以自定扩展和操作引擎Spider中间通信的功能组件(比如进入SpiderResponses;和从Spider出去的Requests)

scrapy架构流程

  • (1)引擎 ‐‐‐》自动运行,无需关注,会自动组织所有的请求对象,分发给下载器

  • (2)下载器 ‐‐‐》从引擎处获取到请求对象后,请求数据

  • (3)Spiders ‐‐‐》Spider类定义了如何爬取某个(或某些)网站。包括了爬取的动作(例如:是否跟进链接)以及如何从网页的内容中提取结构化数据(爬取item)。 换句话说,Spider就是您定义爬取的动作及分析某个网页(或者是有些网页)的地方。

  • (4)调度器 ‐‐‐》有自己的调度规则,无需关注

  • (5)管道(Item Pipeline) ‐‐‐》最终处理数据的管道,会预留接口供我们处理数据.当Item在Spider中被收集之后,它将会被传递到Item Pipeline,一些组件会按照一定的顺序执行对Item的处理。每个itempipeline组件(有时称之为**“Item Pipeline”**)是实现了简单方法的Python类。他们接收到Item并通过它执行一些行为,同时也决定此Item是否继续通过pipeline,或是被丢弃而不再进行处理。

  • 以下是item pipeline的一些典型应用:

    1. 清理HTML数据

    2. 验证爬取的数据(检查item包含某些字段)

    3. 查重(并丢弃)

    4. 将爬取结果保存到数据库中

在这里插入图片描述

Scrapy项目的运行流程

代码写好,程序开始运行

  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也会重新下载。)

制作Scrapy爬虫一共四步

  • 新建项目(scrapy start project xxx):新建一个新的爬虫项词。

  • 明确目标(编写items.py):明确你想要抓取的目标。

  • 制作爬虫(spiders/xxspider.py):制作爬虫开始爬取网页。

  • 存储内容(pipelines.py):设计管道存储爬取内容。

1.创建scrapy项目:

  终端输入  `scrapy startproject  项目名称`    

2.项目组成:

​ spiders
init.py
​ 自定义的爬虫文件.py ‐‐‐》由我们自己创建,是实现爬虫核心功能的文件
init.py

​ items.py ‐‐‐》定义数据结构的地方,是一个继承自scrapy.Item的类
​ middlewares.py ‐‐‐》中间件 代理
​ pipelines.py ‐‐‐》管道文件,里面只有一个类,用于处理下载数据的后续处 理,默认是300优先级,值越小优先级越高(1‐1000)

​ settings.py ‐‐‐》配置文件 比如:是否遵守robots协议,User‐Agent 定义

3.创建爬虫文件:

  • (1)跳转到spiders文件夹 cd 目录名字/目录名字/spiders

  • (2)scrapy genspider 爬虫名字 网页的域名

  • 爬虫文件的基本组成:

    • 继承scrapy.Spider类
      1. name = ‘baidu’ ‐‐‐》 运行爬虫文件时使用的名字
      2. allowed_domains ‐‐‐》爬虫允许的域名,在爬取的时候,如果不是此域名之下的url,会被过滤掉
      3. start_urls ‐‐‐》 声明了爬虫的起始地址,可以写多个url,一般是一个
      4. parse(self, response) ‐‐‐》解析数据的回调函数
        1. response.text ‐‐‐》响应的是字符串
        2. response.body ‐‐‐》响应的是二进制文件
        3. response.xpath() --‐》xpath方法的返回值类型是selector列表
        4. extract() ‐‐‐》提取的是selector对象的是data
        5. extract_first() ‐‐‐》提取的是selector列表中的第一个数据

4.运行爬虫文件:

`scrapy crawl 爬虫名称`

注意:应在spiders文件夹内执行

Scrapy框架终端基本命令

  • scrapy bench 测试性能(pages/min)
    在这里插入图片描述
  • scrapy fetch ‘http://www.baidu.com’ 爬取百度页面的源代码,DEBUG信息(200)表示爬虫程序正常运行
  • genspider 创建爬虫
  • runspider 启动爬虫
  • shell 使用scrapy的shell环境
  • startproject 创建项目
  • version 显示版本
  • view 使用浏览器视图
  • list 显示当前项目有多少个爬虫程序

入门案例

学习目标

  • 创建一个Scrapy项目
  • 定义提取的结构化数据(Item)
  • 编写爬取网站的 Spider 并提取出结构化数据(Item)
  • 编写 Item Pipelines 来存储提取到的Item(即结构化数据)

1.新建爬虫项目

在开始爬取之前,必须创建一个新的Scrapy项目。进入自定义的项目目录中,运行下列命令
scrapy startproject mySpider

2.新建爬虫

跳转到mySpider/mySpider/spiders文件夹下

scrapy genspider 爬虫名 网页域名

网页域名的作用,让爬虫程序只在此域名下爬取

会在spiders文件夹下生成爬虫名.py文件

import scrapy


class ItcastSpider(scrapy.Spider):	#Spider类
    name = "itcast"	# 爬虫名称
    allowed_domains = ["www.itcast.cn"]	# 爬虫运行的域名
    start_urls = ["http://www.itcast.cn"]	# 起始url,爬虫程序启动的第一次请求目的url

    """处理响应数据的方法"""
    def parse(self, response):
        print(response.body)
        # pass

后面还会学习到CrawlSpider

3.运行和检测爬虫程序

  • 先看项目能不能正常运行在修改代码

在这里插入图片描述
在这里插入图片描述

此时,输入scrapy会多一些可用指令

  • scrapy check 爬虫名 检查爬虫是否正常
  • scrapy crawl 爬虫名 启动爬虫程序

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

  • scrapy.cfg :项目的配置文件
  • Itcast/:项目的Python模块,将会从这里引用代码
  • Itcast/items.py:项目的目标文件
  • Itcast/pipelines.py:项目的管道文件
  • Itcast/settings.py:项目的设置文件
  • Itcast/spiders/:存储爬虫代码目录

4.明确目标(Itcast/items.py)

我们打算抓取:http://www.itcast.cn/channel/teacher.shtml 网站里的所有讲师的姓名、职称和个人信息。

  1. 打开mySpider目录下的items.py,
  2. Item 定义结构化数据字段用来保存爬取到的数据,有点像Python中的dict,但是提供了一些额外的保护减少错误。
  3. 可以通过创建一个 scrapy.Item 类,并且定义类型为 scrapy.Field 的类属性来定义一个 Item (可以理解成类似于ORM的映射关系)。
  4. 接下来,创建一个 ItcastItem类,和构建item模型(model)。

5.制作爬虫

爬数据(Itcast/spiders/itcast.py)

在当前目录下输入命令,将在 myspider/spider 目录下创建一个名为 itcast 的爬虫,并指定爬取域的范围:Scrapy genspider itcast "itcast.cn"

打开 Itcast/spider 目录里的 itcast.py,默认增加了下列代码

import scrapy

class ItcastSpider(scrapy.Spider):	
    name = "itcast"	
    allowed_domains = ["www.itcast.cn"]	
    start_urls = ["http://www.itcast.cn"]	

    """处理响应数据的方法"""
    def parse(self, response):
        # pass

其实也可以由我们自行创建 itcast.py 并编写上面的代码,只不过使用命令可以免去编写固定代码的麻烦

要建立一个Spider,你必须用scrapy.Spider类创建一个子类,并确定了三个强制的属性一个方法

  • name = “”: 这个爬虫的识别名称,必须是唯一的,在不同的爬虫必须定义不同的名字。

  • allow_domains = []是搜索的域名范围,也就是爬虫的约束区域,规定爬虫只爬取这个域名下的网
    页,不存在的URL会被忽略。

  • start_ur1s = ():爬取的URL元祖/列表。爬虫从这里开始抓取数据,所以,第一次下载的数据将会从这些URL开始。其他子URL将会从这些起始URL中继承性生成。

  • parse(se1f,response):解析的方法,每个初始URL完成下载后将被调用,调用的时候传入从每一个URL传回的Response对象来作为唯一参数,主要作用如下:

    1. 负责解析返回的网页数据(response.body),提取结构化数据(生成item)
    2. 生成需要下一页的URL请求。

将start_urls的值修改为需要爬取的第一个url

start_urls =("http://ww.itcast.cn/channel/teacher.shtml")

修改parse()方法

def parse(self, response):
	with open("teacher.html","w")as f:
        f.write(response.text)

然后运行一下看看,在Itcast目录下执行

scrapy crawl itcast

是的,就是 itcast,看上面代码,它是 ItcastSpider 类的 name 属性,也就是使用 scrapy genspider 命令的爬虫名。

一个Scrapy爬虫项目里,可以存在多个爬虫。各个爬虫在执行时,就是按照 name 属性来区分。

运行之后,如果打印的日志出现[scrapy]INFO:Spider closed(finished),代表执行完成。之后当前文件夹中就出现了一个 teacher.html 文件,里面就是我们刚刚要爬取的网页的全部源代码信息

# 注意,Python2.x默认编码环境是ASCII码,当和取回的数据编码格式不一致时,可能会造成乱码;
# 我们可以指定保存内容的编码格式,一般情况下,我们可以在代码最上方添加:
import sys
reload(sys)
sys.setdefaultencoding("utf-8")
#这三行代码是Python2.x里解决中文编码的万能钥匙,经过这么多年的吐槽后Python3学乖了,默认编码是Unicode了..
取数据

爬取整个网页完毕,接下来的就是取数据过程了,首先观察

在这里插入图片描述

很明显可以看出网页结构如下图所示

<li>
		<img src="images/teacher/javaee/20220302113627师老师高级讲师2009年入行.jpg">
		<div class="li_txt">
			<h3>师老师</h3>
			<h4>高级讲师</h4>
			<p>13年的项目开发和教育培训经验,精通Java EE的主流开发框架、Oracle和MySQL等关系型数据库。曾在中科院遥感应用研究所、慧点科技、达利本斯等公司担任软件开发工程师、项目总监,带团队做过边防部队、人寿集团、平安集团等多个企业的大型项目,之后在互联网公司知果科技担任开发经理,完成知果果网的核心产品开发。						</p>
			</div>
</li>

所以,直接使用xpath来提取数据

我们先引用Itcast/items.py里面的ItcastItem

from Itcast.items import ItcastItem
导包从项目的根目录下开始

然后将我们得到的数据封装到一个Itcastitem对象中,可以保存每个老师的属性:

import scrapy
from Itcast.Itcast.items import ItcastItem


class ItcastSpider(scrapy.Spider):  # Spider类
    name = "itcast"  # 爬虫名称
    allowed_domains = ["www.itcast.cn"]  # 爬虫运行的域名
    start_urls = [
        "http://www.itcast.cn/channel/teacher.shtml"
    ]  # 起始url,爬虫程序启动的第一次请求目的url

    """处理响应数据的方法"""

    def parse(self, response):
        with open("teacher.html", "w", encoding="utf-8") as f:
            f.write(response.text)

        # 存放老师信息的集合
        items = []
        for each in response.xpath(
            "//div[@class=' tea_txt']//ul//li//div[@class='li_txt']"
        ):	# each是一个结点,对节点使用xpath方法需要加'./'表示当前节点下
            
            # 将我们得到的数据封装成一个ItcastItem对象
            item = ItcastItem()
            # extract()方法返回的都是unicode字符串
            name = each.xpath("./h3/text()").extract()
            title = each.xpath("./h4/text()").extract()
            info = each.xpath("./p/text()").extract()

            # xpath返回的是包含一个元素的列表
            item["name"] = name[0]
            item["title"] = title[0]
            item["info"] = info[0]

            items.append(item)

        # 直接返回最后数据
        return items

我们先不处理管道,后面会详细解释

保存数据

scrapy保存信息的最简单的方法主要有四种,-o输出指定格式的文件,命令如下

如果spiders/itcast.py中使用return 返回的是一个ItcastItem对象,会自动识别给piplines管道来处理数据
如果return的是ItcastItem对象的列表,可以使用以下命令来进行持久化存储

# json格式,默认为Unicode编码
scrapy crawl itcast -o teachers.json
# json lines格式,默认为Unicode编码
scrapy crawl itcast -o teachers.jsonl
# csv 逗号表达式,可用Exce1打开
scrapy crawl itcast -o teachers.csv
# xml格式
scrapy crawl itcast -o teachers.xml
思考

如果将代码改成下面形式,结果完全一样。请思考yield在这里的作用

import scrapy

from Itcast.items import ItcastItem


class ItcastSpider(scrapy.Spider):  # Spider类
    name = "itcast"  # 爬虫名称
    allowed_domains = ["www.itcast.cn"]  # 爬虫运行的域名
    start_urls = [
        "http://www.itcast.cn/channel/teacher.shtml"
    ]  # 起始url,爬虫程序启动的第一次请求目的url

    """处理响应数据的方法"""

    def parse(self, response):
        with open("teacher.html", "w", encoding="utf-8") as f:
            f.write(response.text)

        # 存放老师信息的集合
        # items = []
        for each in response.xpath(
            "//div[@class=' tea_txt']//ul//li//div[@class='li_txt']/"
        ):
            # 将我们得到的数据封装成一个ItcastItem对象
            item = ItcastItem()
            # extract()方法返回的都是unicode字符串
            name = each.xpath("./h3/text()").extract()
            title = each.xpath("./h4/text()").extract()
            info = each.xpath("./p/text()").extract()

            # xpath返回的是包含一个元素的列表
            item["name"] = name[0]
            item["title"] = title[0]
            item["info"] = info[0]
            
            # items.append(item)
            
            # 将获取到的数据交给piplines,后继续回来执行
            yield item

        # 直接返回最后数据
        # return items

  • yield的作用是,执行到yield后,不会像return那样直接返回,函数结束,而是会在返回此次后继续到上次执行的位置继续执行,return每次调用返回的值都一样

  • 带有 yield 的函数不再是一个普通函数,而是一个生成器 generator,可用于迭代

  • yield 是一个类似 return 的关键字,迭代一次遇到 yield 时就返回 yield 后面(右边)的值。重点是:下一次迭代时,从上一次迭代遇到的yield后面的代码(下一行)开始执行

  • 简要理解:yield就是 return 返回一个值,并且记住这个返回的位置,下次迭代就从这个位置后(下一行)开始

管道处理保存数据(piplines.py)

要先在settings.py中启用管道后,itcast.py爬取的数据才会经过piplines管道进行处理保存

# 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
import json


class ItcastPipeline:
    def __init__(self) -> None:
        self.f = open("itcast_piplines.json", "wb")

    def process_item(self, item, spider):  # item就是items.py返回的值
        content = json.dumps(
            dict(item), ensure_ascii=False
        )  # 把字典转换成json格式,ensure_ascii表示不把中文字符串当做ascii码,而是unicode
        self.f.write(content.encode("utf-8"))

        # 返回给引擎,告诉引擎处理完毕,给我下一个
        return item

    def close_spider(self, spider):  # 整个爬虫程序关闭时做的事
        self.f.close()
 	

需要在**items.py**中定义需要的属性

class ItcastItem(scrapy.Item):
    # define the fields for your item here like:
    name = scrapy.Field()  # Field()类似字典
    info = scrapy.Field()  
    title = scrapy.Field()  
    # pass

Item Pipline

当Item在Spider中被收集之后,它将会被传递到Item Pipeline,这些Item Pipeline组件按定义的顺序处理Item.
每个Item Pipeline都是实现了简单方法的Python类,比如决定此Item是丢弃而存储。以下是item pipeline的一些典型应用:

  • 验证爬取的数据(检查item包含某些字段,比如说name字段)
  • 查重(并丢弃)
  • 将爬取结果保存到文件或者数据库中
import something  # 确保这里的something是您实际想要导入的模块或包  
  
class SomethingPipeline(object):  
    def __init__(self):  
        # 可选实现,做参数初始化等  
        # 这里可以添加初始化代码,例如连接数据库等  
        pass  
    
	def process_item(self, item, spider):  
        # item(Item 对象) - 被爬取的item  
        # spider(Spider 对象) - 爬取该item的spider  
        # 这个方法必须实现,每个item pipeline组件都需要调用该方法  
        # 方法必须返回一个 Item 对象,被丢弃的item将不会被之后的pipeline组件处理  
        # 这里可以添加处理item的代码,比如清洗数据、保存数据到数据库等  
        return item
    
    def open_spider(self, spider):  
        # spider(Spider 对象) - 被开启的spider  
        # 可选实现,当spider被开启时,这个方法被调用  
        # 这里可以添加一些在spider启动时需要的初始化操作  
        pass  
    
    def close_spider(self, spider):  
        # spider(Spider 对象) - 被关闭的spider  
        # 可选实现,当spider被关闭时,这个方法被调用  
        # 这里可以添加一些清理代码,比如关闭数据库连接等  
        pass

Scrapy Shell

Scrapy终端是一个交互终端,我们可以在未启动spider的情况下尝试及调试代码,也可以用来测试XPathCSS表达式,查看他们的工作方式,方便我们爬取的网页中提取的数据。

如果安装了IPython,Scrapy终端将使用 IPython(替代标准Python终端)。IPython终端与其他相比更为强大,提供智能的自动补全,高亮输出,及其他特性。(推荐安装IPython)

启动scrapyshell

进入项目的根目录,执行下列命令来启动shell

scrapy shell "http://ww.itcast.cn/channel/teacher.shtml"

在这里插入图片描述

Scrapy Shell根据下载的页面会自动创建一些方便使用的对象,例如Response对象,以及selector对象(对HTML及XML内容)。

当shell载入后,将得到一个包含response数据的本地response变量,输入response.body将输出response的包体,输出response.headers 可以看到response的包头。

输入response.selector 时,将获取到一个response 初始化的类 Selector 的对象,此时可以通过使用 response.selector.xpath()response.selector.css()来对 response 进行查询。

Scrapy也提供了一些快捷方式,例如 response.xpath() response.css()同样可以生效(如之前的案例)。

Selectors选择器

Scrapy Selectors 内置 XPathCss Selector 表达式机制

Selector有四个基本的方法,最常用的还是xpath

  • xpath():传入xpath表达式,返回该表达式所对应的所有节点的selector list列表
  • extract():序列化该节点为Unicode字符串并回list
  • css():传入CSS表达式,返回该表达式所对应的所有节点的selector list列表,语法同 BeautifulSoup4。
  • re():根据传入的正则表达式对数据进行提取,返回Unicode字符串list列表
# 定位页面上的所有链接  
//a  
  
# 定位具有特定类名的元素,例如所有类名为"my-class"的<div>元素  
//div[@class='my-class']  
  
# 如果类名有多个值,定位包含特定类名的元素  
//div[contains(concat(' ', normalize-space(@class), ' '), ' my-class ')]  
  
# 定位包含特定文本的元素,例如所有包含"点击这里"文本的<a>元素  
//a[contains(text(), '点击这里')]  
  
# 定位具有特定属性值的元素,例如所有href属性中包含"example.com"的<a>元素  
//a[contains(@href, 'example.com')]  
  
# 定位某个特定元素下的所有直接子元素,例如<div id="container">下的所有<p>元素  
//div[@id='container']/p  
  
# 定位具有特定id的元素,例如id为"unique-id"的<div>元素  
//div[@id='unique-id']

Spider

Spider类定义了如何爬取某个(或某些)网站。包括了爬取的动作(例如:是否跟进链接)以及如何从网页的内容中提取结构化数据(爬取item)。

换句话说,Spider就是您定义爬取的动作及分析某个网页(或者是有些网页)的地方。

class scrapy.spider 是最基本的类,所有编写的爬虫必须继承这个类。

主要用到的函数及调用顺序为:

  • init_(): 初始化爬虫名字和start_urls列表
  • start_requests(): 调用make_requests_from_url()生成Requests对象交给Scrapy下载并返response
  • parse(): 解析response,并返回 Item 或 Requests (需指定回调函数)。Item传给item pipline持久化 ,而Requests交由Scrapy下载,并由指定的回调函数处理(默认parse(),一直进行循环,直到处理完所有的数据为止。)

源码参考

# 所有爬虫的基类,用户定义的爬虫必须从这个类继承
class Spider(object_ref):
    """Base class for scrapy spiders. All spiders must inherit from this class."""

    name: str
    custom_settings: Optional[dict] = None
    
   # 定义spider名字的字符串(string)。spider的名字定义了scrapy如何定位(并初始化)spider,所以其必须是唯一的
   # name是spider最重要的属性,而且是必须的。
	# 一般做法是以该网站(domain)(加或不加后缀)来命名spider。例如,如果spider爬取mywebsite.com,该spider名为mywebsite

    def __init__(self, name: Optional[str] = None, **kwargs: Any):
        if name is not None:
            self.name = name
        # 如果爬虫没有名字,中断后续操作则报错
        elif not getattr(self, "name", None):
            raise ValueError(f"{type(self).__name__} must have a name")
            
        # python 对象或类型通过内置成员_dict 来存储成员信息
        self.__dict__.update(kwargs)
        
        # URL列表。当没有指定的URL时,spider将从该列表中开始进行爬取。 因此,第一个被获取到的页面的URL将是该列表的内容
        if not hasattr(self, "start_urls"):
            self.start_urls: List[str] = []

    @property
    def logger(self) -> logging.LoggerAdapter:
        logger = logging.getLogger(self.name)
        return logging.LoggerAdapter(logger, {"spider": self})

    # 打印scrapy执行后的1og信息
    def log(self, message: Any, level: int = logging.DEBUG, **kw: Any) -> None:
        """Log the given message at the given log level

        This helper wraps a log call to the logger within the spider, but you
        can use it directly (e.g. Spider.logger.info('msg')) or use any other
        Python logger too.
        """
        self.logger.log(level, message, **kw)

    @classmethod
    def from_crawler(cls, crawler: Crawler, *args: Any, **kwargs: Any) -> Self:
        spider = cls(*args, **kwargs)
        spider._set_crawler(crawler)
        return spider

    # 判断对象object的属性是否存在,不存在做断言处理
    def _set_crawler(self, crawler: Crawler) -> None:
        self.crawler = crawler
        self.settings = crawler.settings
        crawler.signals.connect(self.close, signals.spider_closed)

        
    # 该方法将读取start_urls内的地址,并为每一个地址生成一个Request对象,交给Scrapy下载并返回Response#该方法仅调用一次
    def start_requests(self) -> Iterable[Request]:
        if not self.start_urls and hasattr(self, "start_url"):
            raise AttributeError(
                "Crawling could not start: 'start_urls' not found "
                "or empty (but found 'start_url' attribute instead, "
                "did you miss an 's'?)"
            )
        for url in self.start_urls:
            yield self.make_requests_from_url(url)
            
    def  make_requests_from_url(self,url):
        #start_requests()中调用,实际生成Request的函数。
        #Request对象默认的回调函数为parse(),提交的方式为get
       	return Request(url, dont_filter=True)

    def _parse(self, response: Response, **kwargs: Any) -> Any:
        return self.parse(response, **kwargs)

    
    # 默认的Request对象回调函数,处理返回的response.
    # 生成Item或者Request对象。用户必须实现这个类
    def parse(self, response: Response, **kwargs: Any) -> Any:
        raise NotImplementedError(
            f"{self.__class__.__name__}.parse callback is not defined"
        )

    @classmethod
    def update_settings(cls, settings: BaseSettings) -> None:
        settings.setdict(cls.custom_settings or {}, priority="spider")

    @classmethod
    def handles_request(cls, request: Request) -> bool:
        return url_is_from_spider(request.url, cls)

    @staticmethod
    def close(spider: Spider, reason: str) -> Union[Deferred, None]:
        closed = getattr(spider, "closed", None)
        if callable(closed):
            return cast(Union[Deferred, None], closed(reason))
        return None

    def __repr__(self) -> str:
        return f"<{type(self).__name__} {self.name!r} at 0x{id(self):0x}>"

Python @property装饰器详解 - 贾志文 - 博客园 (cnblogs.com)

【Python】一文弄懂python装饰器(附源码例子)_python 装饰器-CSDN博客

Python 函数装饰器 | 菜鸟教程 (runoob.com)

  • name 定义spider名字的字符串。

    例如,如果spider爬取 mywebsite.com,该spider通常会被命名为 mywebsite

  • allowed domains

    包含了spider允许爬取的域名(domain)的列表,可选。

  • start urls

    初始URL元组/列表。当没有制定特定的URL时,spider将从该列表中开始进行爬取。

  • start requests(self)

    该方法必须返回一个可迭代对象(iterable)。该对象包含了spider用于爬取(默认实现是使用start urls 的

    url)的第一个Request.当spider启动爬取并且未指定start urls时,该方法被调用。

  • parse(self, response)

    当请求url返回网页没有指定回调函数时,默认的Request对象回调函数。用来处理网页返回的response,以及生成Item或者Request对象。

  • log(self,messagel.level, component])

    使用 scrapy.log.msg()方法记录(log)message。更多数据请参见 logging

尝试腾讯招聘案例(普通版)

python爬虫(二十二)scrapy案例–爬取腾讯招聘数据_爬取腾讯社会招聘“数据分析”岗位的所有招聘信息-CSDN博客

我们用腾讯社招的网站搜索 | 腾讯招聘 (tencent.com)举例:

(http://hr.tencent.com/position.php?&start=0#a中#a表示锚点,定位到页面的哪个位置)

首先分析要爬取的数据有哪些

在这里插入图片描述

根节点的xpath路径为//div[@class='recruit-list']/a

初步确定要爬取的信息

职位名positionName: //div[@class='recruit-list']/a/div/span[1]

职位类别positionType: //div[@class='recruit-list']/a/p/span[3]

职位要求positionRequire://div[@class='recruit-list']/a/p/span[5]

工作地点workLocation: //div[@class='recruit-list']/a/div/span[2]

详细介绍positionInfo: //div[@class='recruit-list']/a/p[@class='recruit-text']

最后更新时间updateTime://div[@class='recruit-list']/a/p/span[7]

然后创建爬虫项目和程序
scrapy startproject Tencent
 cd .\Tencent\
scrapy genspider  tencent "tencent.com"
items.py

定义要采集的数据模型

# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html

import scrapy


class TencentItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    # 职位名
    positionName = scrapy.Field()

    # 职位类别
    positionType = scrapy.Field()

    # 职位要求
    positionRequire = scrapy.Field()

    # 工作地点
    workLocation = scrapy.Field()

    # 详细介绍
    positionInfo = scrapy.Field()

    # 最后更新时间
    updateTime = scrapy.Field()

tencent.py
import scrapy
from Tencent.items import TencentItem


class TencentSpider(scrapy.Spider):
    name = "tencent"
    allowed_domains = ["tencent.com"]
    baseUrl = "http://hr.tencent.com/search.html?index="
    index = 1
    start_url = [baseUrl + str(index)]

    def parse(self, response):

        node_list = response.xpath("//div[@class='recruit-list']/a")

        for node in node_list:
            item = TencentItem()
            # 提取每个职位的信息
            item["positionName"] = node.xpath("./div/span[1]/text()").extract()[0]
            item["positionType"] = node.xpath(
                "//div[@class='recruit-list']/a/p/span[3]/text()"
            ).extract()[0]
            item["positionRequire"] = node.xpath(
                "//div[@class='recruit-list']/a/p/span[5]/text()"
            ).extract()[0]
            item["workLocation"] = node.xpath("./div/span[2]/text()").extract()[0]
            item["positionInfo"] = node.xpath(
                "//div[@class='recruit-list']/a/p[@class='recruit-text']/text()"
            ).extract()[0]
            item["updateTime"] = node.xpath(
                "//div[@class='recruit-list']/a/p/span[7]/text()"
            ).extract()[0]

            yield item

         # 此次硬编码,不是最佳方案,可在每一页中提取下一页url
        if self.index < 282:
            self.index += 10
            url = self.baseUrl + str(self.index)
            yield scrapy.Request(url=url, callback=self.parse)

    # def parse_next(self,response):
    #     pass

  • start_url = [baseUrl + str(index)]:拼接请求网址
  • yield scrapy.Request(url=url, callback=self.parse): 对新网址发送请求,callback函数可自己定义
  • yield会把item/Request返回给引擎,引擎判断是交给管道保存还是继续进入请求队列
piplines.py
# 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
import json
from itemadapter import ItemAdapter


class TencentPipeline:
    def __init__(self):
        self.f = open("data.json", "wb")

    def process_item(self, item, spider):
        print("\n" + item)
        self.f.write(
            (json.dumps(dict(item), ensure_ascii=False) + ",\n").encode("utf-8")
        )
        return item

    def close_spider(self, spider):
        self.f.close()

腾讯招聘(抓包)

什么是抓包

在网络爬虫的上下文中,抓包技术可以被用来分析和优化爬虫的性能。具体来说,爬虫开发者可以使用抓包工具(如

Wireshark、tcpdump等)来捕获爬虫程序与服务器之间的通信数据包。通过对这些数据包的分析,开发者可以了解爬虫

请求的发送情况、服务器的响应情况,以及请求和响应中携带的具体数据内容。

分析请求

打开腾讯招聘页面,右键源代码,随便搜索一个网页中的职位名称,结果为空,说明,职位信息是动态响应到界面中的;

此时,就要到网络中去抓包,按F11打开调试工具,点到网络项,XHR,然后刷新界面,逐个检查发现Query开头的请求返回的响应数据(预览)中有我们想要的数据.

复制请求url,在另一窗口打开,不断删除参数,最后发现,只需要简单的https://careers.tencent.com/tencentcareer/api/post/Query?pageIndex=281&pageSize=10就能访问到数据,再将相隔两页对比数据,发现pageIndex的值就是当前页面的索引,pageSize固定为10(每页展示的招聘信息总数)

在这里插入图片描述

所以,我们试探性的将pageIndex改为1,果然发现获取到了第一页的招聘信息

在这里插入图片描述

爬取数据

把上面请求的响应数据放到json解析器中去,解析想要的数据所处的格式

在这里插入图片描述

编写代码

spiders/tencent.py

import json
import scrapy
from Tencent.items import TencentItem


class TencentSpider(scrapy.Spider):
    name = "tencent"
    allowed_domains = ["tencent.com"]
    index = 1
    start_urls = [
        f"http://careers.tencent.com/tencentcareer/api/post/Query?pageIndex={index}&pageSize=10"
    ]

    def parse(self, response):
        print("======================================")	# 输出一行,让你更容易看到输出信息,后面写完项目可以注释掉
        print(response.text)	# 返回json数据
        data_dict = response.json()	# 把json数据变成python字典类型
        RecruitPostName = data_dict["Data"]["Posts"][0]["RecruitPostName"]	# 解嵌套

        print(RecruitPostName)	
        print("======================================")

经过一系列操作,最后成功获得想要的第一个职位信息

在这里插入图片描述

最终代码为

tencent.py
import json
import scrapy
from Tencent.items import TencentItem


class TencentSpider(scrapy.Spider):
    name = "tencent"
    allowed_domains = ["tencent.com"]
    index = 1
    start_urls = [
        f"http://careers.tencent.com/tencentcareer/api/post/Query?pageIndex={index}&pageSize=10"
    ]

    def parse(self, response):

        print(response.text)
        data_dict = response.json()
        datas = data_dict["Data"]["Posts"]  # 返回一个数组

        for data in datas:
            item = TencentItem()
            item["positionName"] = data["RecruitPostName"]
            item["workLocation"] = data["LocationName"]
            item["positionType"] = data["CategoryName"]
            item["positionInfo"] = data["Responsibility"]
            item["PostURL"] = data["PostURL"]
            item["updateTime"] = data["LastUpdateTime"]

            yield item

        if self.index < 282:
            self.index += 1
            url = f"http://careers.tencent.com/tencentcareer/api/post/Query?pageIndex={self.index}&pageSize=10"
            yield scrapy.Request(url=url, callback=self.parse)


# def parse_next(self,response):
#     pass

items.py
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html

import scrapy


class TencentItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    # 职位名
    positionName = scrapy.Field()

    # 职位类别
    positionType = scrapy.Field()

    # 职位详情url
    PostURL = scrapy.Field()

    # 工作地点
    workLocation = scrapy.Field()

    # 详细介绍
    positionInfo = scrapy.Field()

    # 最后更新时间
    updateTime = scrapy.Field()

piplines.py
# 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
import json
from itemadapter import ItemAdapter


class TencentPipeline:
    def __init__(self):
        self.f = open("data.json", "w")

    def process_item(self, item, spider):
        print("\n" + str(item))
        x = self.f.write((json.dumps(dict(item), ensure_ascii=False) + ",\n"))
        print(f"成功写入{x}个字符")
        return item

        # def close_spider(self, spider):
        self.f.close()

运行结果展示

在这里插入图片描述

CrawlSpider

独门秘笈

  1. 继承自scrapy.Spider

  2. 独门秘笈
    CrawlSpider可以定义规则,再解析html内容的时候,可以根据链接规则提取出指定的链接,然后再向这些链接发

    送请求所以,如果有需要跟进链接的需求,意思就是爬取了网页之后,需要提取链接再次爬取,使用CrawlSpider

    是非常合适的

  3. 提取链接
    链接提取器,在这里就可以写规则提取指定链接

    scrapy.linkextractors.LinkExtractor( 
        # 正则表达式  提取符合正则的链接
        allow = (), 
        # (不用)正则表达式  不提取符合正则的链接
        deny = (),     
        #(不用)允许的域名  
        allow_domains = (),  
        #(不用)不允许的域名   
        deny_domains = (),
        # xpath,提取符合xpath规则的链接
        restrict_xpaths = (),      
        # 提取符合选择器规则的链接
        restrict_css = ()         
       
    
  4. 模拟使用

    • 正则用法:links1 = LinkExtractor(allow=r'list 23 \d+\.html')
    • xpath用法:links2 = LinkExtractor(restrict xpaths=r'//div[@class="x"]')
    • css用法:links3 = LinkExtractor(restrict css='.x')
  5. 提取连接
    link.extract_links(response)

  6. 注意事项
    【注1】callback只能写函数名字符串,callback=‘parse item’
    【注2】在基本的spider中,如果重新发送请求,那里的callback写的是callback=self.parse_item

  7. fol在这里插入图片描述
    low=true 是否跟进 就是按照提取连接规则进行提取

CrawlSpider案例

read.py

from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from scrapy_dushuwang.items import ScrapyDushuwangItem


class ReadSpider(CrawlSpider):
    name = "read"
    allowed_domains = ["www.dushu.com"]
    start_urls = ["https://www.dushu.com/book/1107.html"]
    # 正则表达式r"/book/\d+\.html"
    rules = (Rule(LinkExtractor(allow=r"/book/\d+\.html"),
                  callback="parse_item",
                  follow=False))  # follow是要不要继续提取

    def parse_item(self, response):
        print("======================")
        # 第一张图片没有懒加载,所以要加入图片源
        # img_list[0] = "https://a.dushu.com/img/n142.png"
        img_list = response.xpath('//div[@class="bookslist"]/ul//img/@data-original')
        name_list = response.xpath('//div[@class="bookslist"]/ul//a/@title')

        for item in range(len(img_list)):
            if item == 1:
                name = name_list.extract_first()
                src = "https://a.dushu.com/img/n142.png"
            else:
                name = name_list[item].extract()
                src = img_list[item].extract()

            book = ScrapyDushuwangItem(name=name, src=src)
            yield book
# item = {} # 建议使用在items文件中定义数据结构
# # item["domain_id"] = response.xpath('//input[@id="sid"]/@value').get()
# # item["name"] = response.xpath('//div[@id="name"]').get()
# # item["description"] = response.xpath('//div[@id="description"]').get()
# return item

# 创建新的请求并重新执行提取
# next_page = response.xpath('//a[contains(text(), "下一页")]/@href').get()  # 获取下一页的链接
# if next_page:
# yield scrapy.Request(next_page, callback=self.parse_item)  # 重新执行提取,回调函数为 self.parse_item

items.py

# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html

import scrapy


class ScrapyDushuwangItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    name = scrapy.Field()
    src = scrapy.Field()

piplines.py

# 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


class ScrapyDushuwangPipeline:

    def open_spider(self, spider):
        self.fp = open('book.json', 'w', encoding='utf-8')

    def process_item(self, item, spider):
        self.fp.write(str(item))
        return item

    def close_spider(self, spider):
        self.fp.close()


import pymysql
# 加载settings文件
from scrapy.utils.project import get_project_settings


class MySqlPipline:

    def open_spider(self, spider):
        settings = get_project_settings()
        self.host = settings['DB_HOST']
        self.port = settings['DB_PORT']
        self.user = settings['DB_USER']
        self.password = settings['DB_PASSWORD']
        self.name = settings['DB_NAME']
        self.charset = settings['DB_CHARSET']

        self.connect()

    def connect(self):
        self.conn = pymysql.connect(
            host=self.host,
            port=self.port,
            user=self.user,
            password=self.password,
            db=self.name,
            charset=self.charset
        )

        self.cursor = self.conn.cursor()

    def process_item(self, item, spider):
        sql = 'insert into book(name,src) values("{}","{}")'.format(item['name'], item['src'])
        # 执行sql语句
        self.cursor.execute(sql)
        # 提交
        self.conn.commit()

        return item

    def close_spider(self, spider):
        self.cursor.close()
        self.conn.close()

settings.py

# Scrapy settings for scrapy_dushuwang project
#
# For simplicity, this file contains only settings considered important or
# commonly used. You can find more settings consulting the documentation:
#
#     https://docs.scrapy.org/en/latest/topics/settings.html
#     https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
#     https://docs.scrapy.org/en/latest/topics/spider-middleware.html

BOT_NAME = "scrapy_dushuwang"

SPIDER_MODULES = ["scrapy_dushuwang.spiders"]
NEWSPIDER_MODULE = "scrapy_dushuwang.spiders"

# 连接mysql参数
DB_HOST = '127.0.0.1'
# DB_HOST = 'localhost'
DB_PORT = 3306
DB_USER = 'root'
DB_PASSWORD = 'Xubin159753123'
DB_NAME = "spider01"
# 注意utf-8的-不允许写
DB_CHARSET = 'utf8'

# Crawl responsibly by identifying yourself (and your website) on the user-agent
# USER_AGENT = "scrapy_dushuwang (+http://www.yourdomain.com)"

# Obey robots.txt rules
ROBOTSTXT_OBEY = True

# Configure maximum concurrent requests performed by Scrapy (default: 16)
# CONCURRENT_REQUESTS = 32

# Configure a delay for requests for the same website (default: 0)
# See https://docs.scrapy.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
# DOWNLOAD_DELAY = 3
# 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 https://docs.scrapy.org/en/latest/topics/spider-middleware.html
# SPIDER_MIDDLEWARES = {
#    "scrapy_dushuwang.middlewares.ScrapyDushuwangSpiderMiddleware": 543,
# }

# Enable or disable downloader middlewares
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
# DOWNLOADER_MIDDLEWARES = {
#    "scrapy_dushuwang.middlewares.ScrapyDushuwangDownloaderMiddleware": 543,
# }

# Enable or disable extensions
# See https://docs.scrapy.org/en/latest/topics/extensions.html
# EXTENSIONS = {
#    "scrapy.extensions.telnet.TelnetConsole": None,
# }

# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
    "scrapy_dushuwang.pipelines.ScrapyDushuwangPipeline": 300,
    # MySqlPipline:"
    "scrapy_dushuwang.pipelines.MySqlPipline": 301
}

# Enable and configure the AutoThrottle extension (disabled by default)
# See https://docs.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 https://docs.scrapy.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"

# Set settings whose default value is deprecated to a future-proof value
REQUEST_FINGERPRINTER_IMPLEMENTATION = "2.7"
TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"
FEED_EXPORT_ENCODING = "utf-8"

日志信息和日志等级

  • 日志级别:

    • CRITICAL: 严重错误

    • ERROR: 一般错误

    • WARNING: 警告

    • INFO: 一般信息

    • DEBUG: 调试信息

    默认的日志等级是DEBUG,只要出现了DEBUG或者DEBUG以上等级的日志,那么这些日志将会打印

  • settings.py文件设置:

    默认的级别为 DEBUG,会显示上面所有的信息

    在配置文件中settings.py

    LOG FILE: 将屏幕显示的信息全部记录到文件中,屏幕不再显示,注意文件后缀一定是.log

    LOG LEVEL: 设置日志显示的等级,就是显示哪些,不显示哪些

scrapy的post请求

  1. 重写start requests方法:

    def start requests(self)
    
  2. start requests的返回值:

    scrapy.FormRequest(url=url.headers=headers, callback=self.parse item, formdata=data)

    • url: 要发送的post地址
    • headers: 可以定制头信息
    • callback: 回调函数
    • formdata: post所携带的数据,这是一个字典

代理

settings.py中,打开一个选项DOWNLOADER MIDDLEWARES={postproject.middlewares.Proxy':543,}

middlewares.py中写代码

def process_request(self, request, spider):
	request.meta['proxy']='https://113.68.202.10:9999'
	return None
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

0zxm

祝大家天天开心

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

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

打赏作者

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

抵扣说明:

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

余额充值