一、Scrapy 基础知识

Scrapy 是适用于 Python 的一个快速、高层次的屏幕抓取和 web 抓取框架,用于抓取 web 站点并从页面中提取结构化的数据。Scrapy 用途广泛,可以用于数据挖掘、监测和自动化测试。
Scrapy 是一个框架,可以根据需求进行定制。它也提供了多种类型爬虫的基类,如 BaseSpider、sitemap 爬虫等,最新版本又提供了 web2.0 爬虫的支持。

1、Scrapy 基本模块

(1) 调度器(Scheduler)

调度器,说白了把它假设成为一个URL(抓取网页的网址或者说是链接)的优先队列,由它来决定下一个要抓取的网址是什么,同时去除重复的网址(不做无用功)。用户可以自己的需求定制调度器。

(2) 下载器(Downloader)

下载器,是所有组件中负担最大的,它用于高速地下载网络上的资源。Scrapy 的下载器代码不会太复杂,但效率高,主要的原因是 Scrapy 下载器是建立在 twisted 这个高效的异步模型上的(其实整个框架都在建立在这个模型上的)。

(3) 爬虫(Spider)

爬虫,是用户最关心的部份。用户定制自己的爬虫(通过定制正则表达式等语法),用于从特定的网页中提取自己需要的信息,即所谓的实体(Item)。例如使用 Xpath 提取感兴趣的信息。
用户也可以从中提取出链接,让Scrapy继续抓取下一个页面。

(4) 实体管道(Item Pipeline)

实体管道,用于接收网络爬虫传过来的数据,以便做进一步处理。例如验证实体的有效性、清除不需要的信息、存入数据库(持久化实体)、存入文本文件等

(5) Scrapy引擎(Scrapy Engine)

Scrapy 引擎是整个框架的核心,用来处理整个系统的数据流,触发各种事件。它用来控制调试器、下载器、爬虫。实际上,引擎相当于计算机的CPU,它控制着整个流程。

(6) 中间件

整个 Scrapy 框架有很多中间件,如下载器中间件、网络爬虫中间件等,这些中间件相当于过滤器,夹在不同部分之间截获数据流,并进行特殊的加工处理。

2、Scrapy 工作流程

流程如下:

1)爬虫中起始的 URL 构造成 Requests 对象 ⇒ 爬虫中间件 ⇒ 引擎 ⇒ 调度器;
2)调度器把 Requests ⇒ 引擎 ⇒ 下载中间件 ⇒ 下载器;
3)下载器发送请求,获取 Responses 响应 ⇒ 下载中间件 ⇒ 引擎 ⇒ 爬虫中间件 ⇒ 爬虫;
4)爬虫提取 URL 地址,组装成 Requests 对象 ⇒ 爬虫中间件 ⇒ 引擎 ⇒ 调度器,重复步骤2;
5)爬虫提取数据 ⇒ 引擎 ⇒ 管道处理和保存数据;

每个模块的具体作用:

这里是引用

3、Scrapy 依赖的 python 包

Scrapy 是用纯python编写的,它依赖于几个关键的python包(以及其他包):

(1)lxml:一个高效的XML和HTML解析器
(2)parsel :一个写在lxml上面的html/xml数据提取库,
(3)w3lib :用于处理URL和网页编码的多用途帮助程序
(4)twisted:异步网络框架
(5)cryptography 和 pyOpenSSL :处理各种网络级安全需求

4、XPath

写爬虫最重要的是解析网页的内容,这个部分就介绍下通过 XPath 来解析网页,提取内容。

(1)HTML 节点和属性

(2)XPath 常用规则

● nodename选取此节点的所有子节点
● /从当前节点选取直接子节点
● //从当前节点选取子孙节点
● .选取当前节点
● …选取当前节点的父节点
● @选取属性

二、Scrapy 框架安装

1、Scrapy 安装

(1)Anaconda Python

conda install scrapy

(2)windows 标准 Python 开发环境

pip install scrapy

(3)ubuntu 标准 Python 开发环境

pip install scrapy  # 或 pip3 install scrapy

验证安装成功:

输入下面的语句,如果未抛出异常,说明 Scrapy 已经安装成功。

Windows 安装验证中遇到的问题:

(1)ImportError: DLL load failed: 找不到指定的模块。


解决方法:lxml 版本与 Scrapy 版本不匹配,更新 lxml 版本

pip install lxml --upgrade

(2)TypeError: attrs() got an unexpected keyword argument ‘eq’


解决方法:attrs 的版本不够,更新 attrs 版本

pip install attrs --upgrade

2、Scrapy Shell

Scrapy 提供了一个 Shell 相当于 Python 的 REPL 环境,可以用这个 Scrapy Shell 测试 Scrapy 代码。在 Windows 中打开黑窗口,执行 scrapy shell 命令,就会进入 Scrapy Shell。

Scrapy Shell 和 Python 的 REPL 环境差不多,也可以执行任何的 Python 代码,只是又多了对 Scrapy 的支持,例如,在 Scrapy Shell 中输入 10 + 20,然后回车,会输出 30,如下图所示:

(1)使用 Scrapy Shell 抓取网页

启动 Chrome 浏览器,进入 淘宝首页 然后在页面右键菜单中单击 检查 命令,在弹出的调试窗口中选择第一个 Elements 标签页,最后单击 Elements 左侧黑色箭头的按钮,将鼠标放到淘宝首页的导航条聚划算上,如下图所示。

这时,Elements 标签页中的 HTML 代码会自动定位到包含 聚划算 的标签上,然后在右键菜单中单击如下图所示的 Copy ⇒ Copy Xpath命令,就会复制当前标签的 Xpath。
很明显,包含 聚划算 文本的是一个 a 标签,复制的 a 标签的 Xpath 如下:

/html/body/div[3]/div/ul[1]/li[2]/a 

在 Scrapy Shell 中启动淘宝首页。

scrapy shell https://www.taobao.com 

在 Scrapy Shell 中要使用 response.xpath 方法测试 Xpath 。

response.xpath('/html/body/div[3]/div/ul[1]/li[2]/a/text()').extract()

上面的代码输出的是一个列表:

如果要直接返回 聚划算 ,需要使用下面的代码:

response.xpath('/html/body/div[3]/div/ul[1]/li[2]/a/text()').extract()[0]

(2)运行 scrapy demo 时报错:[twisted] CRITICAL: Unhandled error in Deferred

报错页面: 原因:是 sqlite 的问题,没有这个包

cmd 下进入python命令,导入这个包试试:
解决方法:导入 sqlite3.def 和 sqlite3.dll 两个文件到本地的 python 环境下的 DLLs包,比如 C:**\Anaconda2\DLLs 文件夹 。
sqlite下载:sqlite下载官方网址

下载解压后放在 python 环境下的 DDLs 包。

三、用 Scrapy 编写网络爬虫

1、创建 Scrapy 工程

(1)Scrapy 框架提供了一个 scrapy 命令用来建立 Scrapy 工程,命令如下:

scrapy startproject 工程名

(2)Scrapy 框架提供了一个 scrapy 命令用来建立爬虫文件,爬虫文件为主要的代码作业文件,通常一个网站的爬取动作都会在爬虫文件中进行编写。命令如下:

该命令会在当前文件下建立 Scrapy 工程,因此建议在自定的目录下打开 CMD 命令并创建工程。

cd 工程名
scrapy genspider 爬虫文件名 待爬取的网站域名

创建后目录大致如下:


在 spiders 目录中建立了一个 first_spider.py 脚本文件,这是一个 Spider 程序,在该程序中会指定要抓取的 Web 资源的 URL。

示例:

创建 Scrapy 工程及爬虫文件

生成的目录和文件结果如下:

2、各目录文件详解

(1)爬虫文件

spiders 下的 jingding.py 是 scrapy 自动为我们生成的爬虫文件。

class scrapy.Spider 是最基本的类,所有编写的爬虫必须继承这个类。 常用属性和方法如下:

● name:定义spider名字的字符串。
● allowed_domains:包含了spider允许爬取的域名(domain)的列表,可选。
● start_urls:初始URL元组/列表。当没有制定特定的URL时,spider将从该列表中开始进行爬取。
● start_requests(self):调用 make_requests_from url() 生成 Requests 对象交给 Scrapy 下载并返回
● response。该方法必须返回一个可迭代对象(iterable),该对象包含了spider用于爬取(默认实现是使用 start_urls 的url)的第一个Request。当spider启动爬取并且未指定start_urls时,该方法被调用
● parse(self, response):每抓取一个URL对应的 Web资源,就会调用该方法,解析 response,并返回 Item 或 Requests(需指定回调函数)。Item 传给 Item pipline 持久化 , 而 Requests 交由 Scrapy
下载,并由指定的回调函数处理(默认parse()),一直进行循环,直到处理完所有的数据为止。当请求 url 返回网页没有指定回调函数时,则该函数作为默认的 Request 对象回调函数,用来处理网页返回的 response,以及生成 Item 或者 Request 对象

Request 对象的用法:

yield Request(url[, callback, method='GET', headers, body, cookies, meta, encoding='utf-8', priority=0, dont_filter=False, errback, flags])

url:请求的 url;
callback:回调函数,用于接收请求后的返回信息,若没指定,则默认为 parse() 函数;
meta:用户自定义向回调函数传递的参数,这个参数一般也可在middlewares中处理,键值对形式;

meta = {‘name’ : ‘Zarten’}
回调函数中获取:my_name = response.meta[‘name’]

method:http请求的方式,默认为GET请求,一般不需要指定。若需要POST请求,用FormRequest即可;
headers:请求头信息,一般在settings中设置即可,也可在middlewares中设置;
body:str类型,为请求体,一般不需要设置(get和post其实都可以通过body来传递参数,不过一般不用)
cookies:dict或list类型,请求的cookie

(2)middlewares.py

middlewares.py 定义Spider Middlewares 和 Downloader Middlewares 的实现。
1)Spider Middlewares

Spider 中间件是介入到 Scrapy 的 spider 处理机制的钩子框架,您可以添加代码来处理发送给 Spiders 的 response 及 spider 产生的 item 和 request
要启用 Spider 中间件(Spider Middlewares),就必须在 setting.py 中进行 SPIDER_MIDDLEWARES 设置中。 该设置是一个字典,键为中间件的路径,值为中间件的顺序(order)。

SPIDER_MIDDLEWARES = {
    'myproject.middlewares.CustomSpiderMiddleware': 543, 
    }

SPIDER_MIDDLEWARES 设置会与Scrapy定义的 SPIDER_MIDDLEWARES_BASE 设置合并(但不是覆盖),而后根据顺序(order)进行排序,最后得到启用中间件的有序列表: 第一个中间件是最靠近引擎的,最后一个中间件是最靠近spider的。
关于如何分配中间件的顺序请查看 SPIDER_MIDDLEWARES_BASE 设置,而后根据您想要放置中间件的位置选择一个值。由于每个中间件执行不同的动作,您的中间件可能会依赖于之前(或者之后)执行的中间件,因此顺序是很重要的。
如果您想禁止内置的(在 SPIDER_MIDDLEWARES_BASE 中设置并默认启用的)中间件, 您必须在项目的 SPIDER_MIDDLEWARES 设置中定义该中间件,并将其值赋为 None 。 例如,如果您想要关闭off-site中间件:

 SPIDER_MIDDLEWARES = {
    'myproject.middlewares.CustomSpiderMiddleware': 543,
    'scrapy.contrib.spidermiddleware.offsite.OffsiteMiddleware': None, } 

Spider 中间件方法:

process_spider_input(response, spider) 
# response (Response 对象) – 被处理的response 
# spider (Spider 对象) – 该response对应的spider 

当 response 通过 spider 中间件时,该方法被调用,处理该response。process_spider_input() 应该返回 None 或者抛出一个异常(exception)。 如果其返回 None ,Scrapy将会继续处理该response,调用所有其他的中间件直到spider处理该response。如果其抛出一个异常(exception),Scrapy将不会调用任何其他中间件的 process_spider_input() 方法,并调用request的errback。 errback的输出将会以另一个方向被重新输入到中间件链中,使用 process_spider_output() 方法来处理,当其抛出异常时则带调用process_spider_exception() 。

process_spider_output(response, result, spider) 
# response (Response 对象) – 生成该输出的response 
# result (包含 Request 或 Item 对象的可迭代对象(iterable)) – spider返回的result 
# spider (Spider 对象) – 其结果被处理的spider 

当 Spider 处理 response 返回 result 时,该方法被调用。process_spider_output() 必须返回包含 Request 或 Item 对象的可迭代对象(iterable)。

process_spider_exception(response, exception, spider) 
# response (Response 对象) – 异常被抛出时被处理的response 
# exception (Exception 对象) – 被跑出的异常 
# spider (Spider 对象) – 抛出该异常的spider

当 spider (或其他spider中间件的) process_spider_input() 抛出异常时, 该方法被调用。process_spider_exception() 必须要么返回 None , 要么返回一个包含 Response 或 Item 对象的可迭代对象(iterable)。 如果其返回 None ,Scrapy 将继续处理该异常,调用中间件链中的其他中间件的 process_spider_exception() 方法,直到所有中间件都被调用,该异常到达引擎(异常将被记录并被忽略)。如果其返回一个可迭代对象,则中间件链的 process_spider_output() 方法被调用, 其他的 process_spider_exception() 将不会被调用。

2)Download Middlewares

下载器中间件是引擎和下载器之间通信的中间件:当引擎传递请求给下载器的过程中,下载中间件可以对请求进行处理 (例如增加http header信息,增加proxy信息等);在下载器完成http请求,传递响应给引擎的过程中, 下载中间件可以对响应进行处理(例如进行gzip的解压等)
在这个中间件中我们可以设置代理、更换请求头信息等来达到反反爬虫的目的。要写下载器中间,可以在下载器中实现两个方法。一个是process_request(self, request, spider),这个方法是在请求发送之前会执行,还有一个是process_response(self, request, response, spider),这个方法是数据下载到引擎之前执行。
要激活下载器中间件组件,就必须在 setting.py 中进行 DOWNLOADER_MIDDLEWARES 设置。 该设置是一个字典(dict),键为中间件类的路径,值为其中间件的顺序(order)。

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

方法:

process_request(self, request, spider)
# request : 发送请求的request对象。
# spider : 发送请求的spider对象。 

这个方法是在下载器发送请求之前会执行的,一般可以在这个里卖弄设置随机代理,随机请求头等。
返回值:

返回Node:如果返回None,Scrapy将继续吃力该request,执行中间件中的相应的方法,知道合适的下载器处理函数被调用。
返回Response对象:Scrapy将不会调用其他的process_request方法,将直接返回这个response对象。已经激活的中间件的process_response()方法则会在每个response返回时被调用。
返回Request对象:不再使用之前的request对象去下载数据,而是根据限制返回request对象返回数据。
如果这个方法中抛出了异常,则会调用process_exception方法。

process_response(self, request, response, spider)
# request:request对象。
# response:被处理的response对象。
# spider:spider对象. 

这个方法是下载器下载的数据到引擎中间会执行的方法。
返回值:

返回Response对象:会将这个新的response对象传给其他中间件,最终传给爬虫。
返回Request对象:下载器链被切断,返回的resquest会重新被下载器调度下载。
如果这个方法中抛出了异常,那么将会调用request的errorback方法,如果没有指定这个方法,那么会抛出一个异常。

(3)settings.py

settings.py 是 spdier 项目的配置文件。

各字段说明如下:

● BOT_NAME:项目名;
● USER_AGENT:默认是注释的,这个东西非常重要,如果不写很容易被判断为电脑,简单点设置一个Mozilla/5.0即可;
● ROBOTSTXT_OBEY:是否遵循机器人协议,默认是true,需要改为 false,否则很多东西爬不了;
● CONCURRENT_REQUESTS:最大并发数,就是同时允许开启多少个爬虫线程;
● DOWNLOAD_DELAY:下载延迟时间,单位是秒,控制爬虫爬取的频率,根据你的项目调整,不要太快也不要太慢,默认是3秒,即爬一个停3秒,设置为1秒性价比较高,如果要爬取的文件较多,写零点几秒也行;
● COOKIES_ENABLED:是否保存 COOKIES,默认关闭,开机可以记录爬取过程中的 COOKIE,非常好用的一个参数;
● DEFAULT_REQUEST_HEADERS:默认请求头,上面写了一个USER_AGENT,其实这个东西就是放在请求头里面的,这个东西可以根据你爬取的内容做相应设置;

● ITEM_PIPELINES:项目管道,300为优先级,越低越爬取的优先度越高

(3)items.py

items 提供一个字段存储, spider 会将数据存在这里
爬虫爬取的主要目标是从非结构化数据源中提取结构化数据,通常是web页面。作为Python语言,Scrapy spiders 可以返回提取的数据。虽然方便而又熟悉,但 Python 却缺乏结构,特别是在一个包含许多 spider 的大型项目中在字段名中输入错误或返回不一致的数据。
为了定义常见的输出数据格式,scrapy 提供了 item 类,Item 对象是用来收集提取数据的简单容器。它们提供了一个类似于字典的API,提供了一种方便的语法,用于声明可用字段
各种各样的 scrapy 组件使用由 item 提供的附加信息,查看已声明的字段,以找出导出的列,可以使用 item 字段元数据定制序列化,trackref跟踪项目实例以帮助发现内存泄漏。
Item 使用简单的类定义语法和字段对象声明,如下所示:

import scrapy

class Product(scrapy.Item): 
	# 字段类型就是简单的scrapy.Field
    name = scrapy.Field()
    price = scrapy.Field()
    stock = scrapy.Field()
    last_updated = scrapy.Field(serializer=str) 

Field 对象用于为每个字段指定元数据,您可以为每个字段指定任何类型的元数据,对 Field 对象的值没有限制。需要注意的是,用于声明该项的字段对象不会被分配为类属性。相反,可以通过Item.fields访问它们。

(4)使用 Item:

1)创建 items

product = Product(name='Desktop PC', price=1000)
print product
# Product(name='Desktop PC', price=1000) 

2)获取 Field 值

product['name']
# Desktop PC

product.get('name')
# Desktop PC

product['last_updated']
# Traceback (most recent call last):
#     ...
# KeyError: 'last_updated'

product.get('last_updated', 'not set')
# not set

product['lala'] # getting unknown field
# Traceback (most recent call last):
#     ...
# KeyError: 'lala'

product.get('lala', 'unknown field')
# 'unknown field'

'name' in product  
# True

'last_updated' in product  
# False

'last_updated' in product.fields 
# True

'lala' in product.fields 
# False 

3)设置 Field 值

product['last_updated'] = 'today' 
product['last_updated']
# today

product['lala'] = 'test' # setting unknown field
# Traceback (most recent call last):
#     ...
# KeyError: 'Product does not support field: lala' 

4)获取所有内容

product.keys()
# ['price', 'name']

product.items()
# [('price', 1000), ('name', 'Desktop PC')]

5)复制 items

product2 = Product(product) 
print product2
# Product(name='Desktop PC', price=1000)

product3 = product2.copy() print product3
# Product(name='Desktop PC', price=1000) 

6)从items创建字典

dict(product) # create a dict from all populated values
# {'price': 1000, 'name': 'Desktop PC'} 

7)从字典创建 items

Product({'name': 'Laptop PC', 'price': 1500})
# Product(price=1500, name='Laptop PC')

Product({'name': 'Laptop PC', 'lala': 1500}) # warning: unknown field in dict
# Traceback (most recent call last):
#    ...
# KeyError: 'Product does not support field: lala' ```

(5)pipelines.py

Item pipline 的主要责任是负责处理爬虫从网页中抽取的 Item,他的主要任务是清洗、验证和存储数据。当页面被蜘蛛解析后,将被发送到 Item pipline,并经过几个特定的次序处理数据。
每个 Item pipline 的组件都是有一个简单的方法组成的 Python 类。获取 Item 并执行方法,同时还需要确定是否需要 Item 管道中继续执行下一步或是直接丢弃掉不处理。简而言之,通过 spider 爬取的数据都会通过这个 pipeline 处理,可以在 pipeline 中执行相关对数据的操作。
每个 item piple 组件是一个独立的 pyhton 类,必须实现 process_item(self,item,spider)方法,每个 item pipeline 组件都需要调用该方法,这个方法必须返回一个具有数据的 dict 或者 item 对象,或者抛出 DropItem 异常,被丢弃的 item 将不会被之后的 pipeline 组件所处理。

    def download_from_url(url):
    response = requests.get(url, stream=True)
    if response.status_code == requests.codes.ok:
        return response.content
    else:
        print('%s-%s' % (url, response.status_code))
        return None
    class SexyPipeline(object):
 
    def __init__(self):
        self.save_path = '/tmp'
 
    def process_item(self, item, spider):
        if spider.name == 'sexy':
            # 取出item里内容
            img_url = item['img_url']
            
            # 业务处理
            file_name = img_url.split('/')[-1]
            content = download_from_url(img_url)
            if content is not None:
                with open(os.path.join(self.save_path, file_name), 'wb') as fw:
                    fw.write(content)
        return item 

1)process_item() 方法的参数有两个:item,是 Item 对象,即被处理的 Item;spider,是 Spider 对象,即生成该 Item 的 Spider。
2) process_item() 方法的返回类型:如果它返回的是 Item 对象,那么此 Item 会被低优先级的 Item Pipeline 的 process_item() 方法处理,直到所有的方法被调用完毕。如果它抛出的是 DropItem 异常,那么此 Item 会被丢弃,不再进行处理。
下面的方法也可以选择实现:

open_spider(self,spider) 

open_spider() 方法是在 Spider 开启的时候被自动调用的。在这里我们可以做一些初始化操作,如开启数据库连接等。其中,参数 spider 就是被开启的 Spider 对象。

close_spider(self,spider)

close_spider() 方法是在 Spider 关闭的时候自动调用的。在这里我们可以做一些收尾工作,如关闭数据库连接等。其中,参数 spider 就是被关闭的 Spider 对象。

from_crawler(cls,crawler) 

from_crawler() 方法是一个类方法,用 @classmethod 标识,是一种依赖注入的方式。它的参数是 crawler,通过 crawler 对象,我们可以拿到 Scrapy 的所有核心组件,如全局配置的每个信息,然后创建一个 Pipeline 实例。参数 cls 就是 Class,最后返回一个 Class 实例。

piplines.py 里的类必须在 settings.py 里的 ITEM_PIPELINES 字段中使用全类名定义,这样才能开启 piplines.py 里的类,否则不能使用

(6)pipeline 的优先级

在 setting.py 中定义各个管道的优先级别,越低越爬取的优先度越高。
比如我的 pipelines.py 里面写了两个管道,一个爬取网页的管道,一个存数据库的管道。

在 setting.py 中调整他们的优先级,如果有爬虫数据,优先执行存库操作。

    'scrapyP1.pipelines.BaiduPipeline': 300,
    'scrapyP1.pipelines.BaiduMysqlPipeline': 200, } 

3、使用 Scrapy 抓取数据,并通过 Xpath 指定解析规则

在 parse 方法中通过 response 参数设置 Xpath,然后从 HTML 代码中过滤出感兴趣的信息,最后将这些信息输出到 Pycharm 的控制台中。

import scrapy
from urllib.parse import urljoin


class BlogSpiderSpider(scrapy.Spider):
    name = 'blog_spider'
    # allowed_domains = ['geekori.com']
    # start_urls = ['http://geekori.com/']
    start_urls = ['https://geekori.com/blogsCenter.php?uid=geekori']

    def parse(self, response):
        # 过滤出指定页面所有的博文
        selector_list = response.xpath('//div[@id="all"]/div[1]/section')
        # 对博文列表进行迭代
        for selector in selector_list:
            blog_dict = {}
            # 1.获取博文标题
            title = selector.xpath('./div[@class="summary"]/h2/a/text()').extract_first()
            blog_dict['title'] = title
            # 2.获取博文的URL
            blog_dict['href'] = urljoin(response.url,
                                        selector.xpath('./div[@class="summary"]/h2/a/@href').extract_first())
            # 3.获取博文的摘要
            blog_dict['abstract'] = selector.xpath('./div[@class="summary"]/p/text()').extract_first()
            print(blog_dict)

4、将抓取到的数据保存为多种格式的文件

执行爬虫文件时添加 -o 选项可以指定保存的文件类型,如

scrapy crawl 爬虫名 -o csvname.csv 
scrapy crawl 爬虫名 -o jsonname.json 

持久化存储对应的文本文件的类型:

(1)基于终端指令的持久化存储

1)blog_spider.py 文件代码:

import scrapy from urllib.parse import urljoin


class BlogSpiderSpider(scrapy.Spider):
    name = 'blog_spider'
    # allowed_domains = ['geekori.com']
    # start_urls = ['http://geekori.com/']
    start_urls = ['https://geekori.com/blogsCenter.php?uid=geekori']

    def parse(self, response):
        # 过滤出指定页面所有的博文
        selector_list = response.xpath('//div[@id="all"]/div[1]/section')
        all_data = []   # 建立空列表,将抓取的数据进行存储
        # 对博文列表进行迭代
        for selector in selector_list:
            blog_dict = {}
            # 1.获取博文标题
            title = selector.xpath('./div[@class="summary"]/h2/a/text()').extract_first()
            blog_dict['title'] = title
            # 2.获取博文的URL
            blog_dict['href'] = urljoin(response.url,
                                        selector.xpath('./div[@class="summary"]/h2/a/@href').extract_first())
            # 3.获取博文的摘要
            blog_dict['abstract'] = selector.xpath('./div[@class="summary"]/p/text()').extract_first()
            all_data.append(blog_dict)
            return(all_data)   # 将存储数据的列表返回 

2)main.py 文件中调用爬虫文件 Scrapy 提供了执行爬虫文件的命令:

scrapy crawl 爬虫名 

为了直接能在 Python 工程中运行网络爬虫,需要建立一个 main.py(文件名可以任意起) 文件,然后使用代码调用执行爬虫命令。

from scrapy.cmdline import execute

import os import sys

sys.path.append(os.path.dirname(os.path.abspath(__file__)))
# 如果要运行其他的网络爬虫,只需修改上面代码中字符串里面的命令即可 
execute(["scrapy", "crawl", "first_spider"," -o"," info.csv"])
# 或者(cmdline.execute("scrapy crawl first_spider -o info.csv -t csv".split()) 

(2)基于管道持久化存储

parse 方法的返回值会被传给 Item Pipeline,并由相应的 Item Pipeline 将数据保存成相应格式的文件。parse 方法必须返回 Item 类型的数据。也就是说,parse 方法的返回值类型必须是 scrapy.Item 或 scrapy.Item 的子类。在该类中会定义要保存的数据对应的属性。
1)blog_spider.py 中代码
pipelines.py 中代码

class BlogsspiderPipeline(object):
    fp = None

    # 重写父类的一个方法: 该方法只在开始爬虫的时候被调用1次
    def open_spider(self, spider):
        print('开始爬虫......')
        self.fp = open('blog.txt', 'w', encoding='utf8')

    # 专门用来处理 item类型的对象,该方法可以接收爬虫脚本提交过来的item对象
    # 注意: 该方法每接收到一个 item就会被调用一次
    def process_item(self, item, spider):
        title = item['title']
        href = item['href']
        abstract = item['abstract']
        self.fp.write(title + '----' + href + '----' + abstract.strip() + '\n')
        return item

    def close_spider(self, spider):
        print('结束爬虫......')
        self.fp.close() 

3)配置文件 setting.py 中开启管道 4)main.py 文件执行爬虫

from scrapy.cmdline import execute

import os import sys

sys.path.append(os.path.dirname(os.path.abspath(__file__)))
# 如果要运行其他的网络爬虫,只需修改上面代码中字符串里面的命令即可 
execute(["scrapy", "crawl", "first_spider","-o"," blog.csv"])
# 或者(cmdline.execute('scrapy crawl first_spider  -o blog.csv'.split()) 

5、数据存放到数据库

6、抓取多个 URL (基于Spider的全站数据爬取)

即将网站中某板块下的全部页码对应的页面数据进行爬取。

(1)将所有页面的url添加到 start_urls 列表

在爬虫类的 start_urls 变量中添加多个 URL,运行爬虫时就会抓取 start_urls 变量中所有的 URL。如下:

class BlogSpiderSpider(scrapy.Spider):
    name = 'blog_spider'
    start_urls = ['https://geekori.com/blogsCenter.php?uid=geekori',
                  'https://geekori.com/blogsCenter.php?uid=geekori&page=2']
    .... 

(2)自行手动进行请求发送

根据要爬取的 url 的变化规律,让代码自动进行改变 url。

import scrapy

class CampusbelleSpider(scrapy.Spider):
    name = 'CampusBelle'
    # allowed_domains = ['www.521609.com']
    start_urls = ['http://www.521609.com/meinvxiaohua/']
    template_url = 'http://www.521609.com/meinvxiaohua/list12%d.html'  # 分析得出url规律
    page_num = 2  # 页码

    def parse(self, response):
       items=TxmoviesItem()
       lists=response.xpath('//div[@class="index_img list_center"]/ul/li"]')
       for i in lists:
           items['name']=i.xpath('./a/@title').get()
           items['description']=i.xpath('./div/div/@title').get()
           yield items    # 把控制权给管道
        
        if self.page_num <= 11:
            new_url = format(self.template_url % self.page_num)
            self.page_num += 1
            yield scrapy.Request(url=new_url, callback=self.parse) 

关于 yield:

程序里一共有两个 yield,类似于中断,作用是移交控制权。
1)第一个 yield:我们对 item 封装数据后,就调用 yield 把控制权给管道,管道拿到 item 后进行处理,处理结束后再回到该程序继续执行后续流程

class TxmoviesPipeline(object):
   def process_item(self, item, spider):
       print(item)
       return item

2)第二个 yield:程序利用了一个回调机制,即 callback,回调的对象是 parse,也就是当前方法,通过不断的回调,程序将陷入循环,直到程序执行完毕。如果不给程序加条件,就会陷入死循环,如本程序我把 if 去掉,那就是死循环了。

yield 与 return 的异同:

共同点:return 和 yield 都用来返回值;在一次性地返回所有值场景中return和yield的作用是一样的。
不同点:如果要返回的数据是通过 for 等循环生成的迭代器类型数据(如列表、元组),return 只能在循环外部一次性地返回,yeild 则可以在循环内部逐个元素返回return 在返回结果后结束函数的运行,而 yield 则是让函数变成一个生成器,生成器每次产生一个值(yield语句),函数被冻结,被唤醒后再产生一个值

7、深度爬取

使用场景:如果爬取解析的数据不在同一张页面中。

需求:爬取 起点中文网 小说的标题以及作者(详情页中)。

import scrapy from QiDianSpider.items import QidianspiderItem


class QidianSpider(scrapy.Spider):
    name = 'qidian'
    # allowed_domains = ['www.xxx.com']
    # TODO 1.替换start_urls列表中的起始url
    start_urls = ['https://www.qidian.com/all']
    template_url = 'https://www.qidian.com/all?orderId=&style=1&pageSize=20&siteid=1&pubflag=0&hiddenField=0&page=%d'
    page_num = 2

    def parse(self, response):
        # TODO 2.数据解析
        li_list = response.xpath('//div[@class="all-book-list"]/div/ul/li')
        for li in li_list:
            # 实例化 QidianspiderItem 对象
            item = QidianspiderItem()
            # 使用xpath提取小说中的标题及详情页的链接
            title = li.xpath('./div[2]/h4/a/text()').extract_first()
            detail_url = response.urljoin(li.xpath('./div[2]/h4/a/@href').extract_first())
            item['title'] = title   # 标题直接放入 Item 中
            # author 需要深度挖掘,打开新的链接页面获得
            yield scrapy.Request(url=detail_url, callback=self.parse_detail, meta={'item': item})
            
        if self.page_num <= 5:
            new_url = format(self.template_url % self.page_num)
            self.page_num += 1
            yield scrapy.Request(url=new_url, callback=self.parse)

    def parse_detail(self, response):
        item = response.meta['item']
        author = response.xpath('//h1/span/a/text()').extract_first()
        item['author'] = author
        yield item    # 把控制权给管道

在 pipelines.py 文件中处理数据,这里为了简单,直接在控制台输出结果。

 class TxmoviesPipeline(object):
 def process_item(self, item, spider):
     print(item)
     return item 

四、示例

来源:IT__learning

物联沃分享整理
物联沃-IOTWORD物联网 » 爬虫框架 Scrapy 详解

发表评论