2021.4.8 投了一个python爬虫岗位 呼~
希望能入职😭

Scrapy框架的使用

之前学习了pyspider框架的用法,可以利用它快速完成爬虫的编写。但是pyspider框架自身存在一些缺点,比如说:

  1. 可配置程度低
  2. 异常处理能力有限
  3. 对于强反爬网站力不从心

而Scrapy框架更加强大, 爬取效率高,扩展组件非常多,可配置、可扩展程度高,应用也最广泛。

介绍

Scrapy基于Twisted的异步处理框架,是纯Python实现的,架构清晰,模块之间耦合度低,非常灵活。同时非常轻便,只需要定制开发几个模块就可以轻松实现一个爬虫。通过多个组件之间的相互协作,不同组件完成各自的任务,以及组件对异步的支持,Scrapy最大限度地提高了网络带宽,提高了数据爬取效率

Scrapy架构

  • Spiders
    定义了爬取的逻辑和网页中抽取的项目,主要负责解析响应并生成提取结果和新的请求

  • Spider Middlewares
    中间件,主要处理爬虫输入的响应和输出的结果以及新的请求

  • Engine
    处理整个系统的数据流、触发事务,是框架的核心

  • Scheduler
    调度器,接受引擎发送的请求并将其加入队列,在引擎再次请求时提供该请求给下载器

  • Downloader
    下载网页内容,并将网页内容返回给爬虫

  • Downloader Middlewares
    主要处理引擎与下载器之间的请求和响应

  • Item Pipeline
    负责处理爬虫从网页中抽取的内容,清洗、验证和存储数据

  • Item
    定义了爬取结果的数据结构,爬取的数据会被赋值成该Item对象

数据流

  1. Engine首先打开一个网站,找到处理该网站的Spider。
  2. Engine从Spider获取第一个要爬取的URL,并通过Scheduler以Request的形式调度
  3. Engine向Scheduler请求下一个要爬取的URL
  4. Scheduler返回下一个要爬取的URL给Engine,后者将该URL通过Downloader Middlewares发送给Downloader
  5. 一旦页面下载完毕,Downloader生成该页面的Response,并将其通过Downloader Middlewares发送给Engine
  6. Engine从下载器中接收到Response,并将其通过Spider Middlewares发送给Spider处理
  7. Spider处理Response,并返回爬取到的Item及新的Request给Engine
  8. Engine将Spider返回的Item给Item Pipeline,将新的Request给Scheduler
  9. 重复第2~第8步,直到Scheduler中没有更多的Request,Engine关闭该网站,爬取结束

使用方法

安装

pip3 install Scrapy

初始化Scrapy项目

初始化一个名为tutorial的scrapy项目:
scrapy startproject tutorial

此时tuorial的目录结构:

创建爬虫

新建一个名为quotes的爬虫爬取quotes.toscrape.com这个网站:
scrapy genspider quotes quotes.toscrape.com

查看quotes.py的内容:

import scrapy

class QuotesSpider(scrapy.Spider):
name = 'quotes'
allowed_domains = ['quotes.toscrape.com']
start_urls = ['http://quotes.toscrape.com/']

def parse(self, response):
pass

可以看到有三个属性:name、allowed_domains、start_urls和一个方法parse

  • name是每个项目唯一的名字
  • allowed_domains是目标网站
  • start_urls是spider启动时爬取的url列表,初始请求由它来定义
  • parse:默认情况下,被调用时start_urls里面的链接构成的请求完成下载执行之后,返回的响应就会作为唯一的参数传递给这个函数。parse函数负责解析返回的响应、提取数据或者进一步生成要处理的请求。

创建Item

Item时保存爬取数据的容器,使用方法与字典类似。

但相比字典多了额外的保护机制,可以避免拼写错误或者定义字段错误

创建Item需要继承scrapy.Item类,并且定义scrapy.Field字段。一般可以获取到的内容有text、author、tags。

定义Item时将items.py修改如下:

import scrapy
class TutorialItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
text = scrapy.Field()

author = scrapy.Field()

tags = scrapy.Field()

解析Response

前面的代码可以看到parse()方法的参数response时start_urls里面的链接爬取后的结果。

所以在parse()方法中,我们可以直接对response变量包含的内容进行解析,从中可以得到网页源代码,找出结果中的链接而得到下一个请求。

网页结构

查看http://quotes.toscrape.com/
其网页结构可以发现,每一页都是由多个class为quote的区块构成,而每个区块又包含text、author、tags。那么可以先找出quote然后提取出里面的内容

后续Request

上面的操作实现了从初始页面抓取内容。下一页的内容由上一页抓取的内容构建下一个请求获得。

底部的Next按钮查看源代码后可以发现链接时/page/2,全链接就是:https://quotes.toscrape.com/page/2
通过这个链接可以构建下一个请求

  • scrapy.Request
    构造请求时需要用到scrapy.Request,这里需要传递两个参数url、callback

    • url: 要请求的链接
    • callback: 回调函数。当指定了该回调函数的请求完成之后,获取到响应,引擎会将该响应作为参数传递给这个回调函数。回调函数进行解析或生成下一个请求,回调函数如parse()所示。
  • 实际上parse()就是解析text、author、tags的方法。下一页的结构和第一页已经解析过的页面结构时一样的,所以可以复用代码。

利用选择器得到下一页链接并生成请求,在parse()方法后追加如下代码:

next = response.css('.pager .next a::attr("href")').extract_first()
url = response.urljoin(next)
yield scrapy.Request(url=url,callback = self.parse)

第一句代码首先通过CSS选择器获取下一个页面的链接,即要获取a超链接中的href属性。这里用到了::attr(href)操作。然后再调用extract_first方法获取内容。

第二句代码调用了urljoin()方法,urljoin()方法可以将相对URL构造成绝对url。

第三句代码通过url和callback变量构造了一个新的请求,回调函数callback依然使用parse()方法。这个请求完成后,响应会重新经过parse方法处理,得到第二页的解析结果。

改写后的Spider类如下:

import scrapy
from tutorial.items import TutorialItem

class QuotesSpider(scrapy.Spider):
name = 'quotes'
allowed_domains = ['quotes.toscrape.com']
start_urls = ['http://quotes.toscrape.com/']

def parse(self, response):
quotes = response.css('.quote')

for quote in quotes:
item = TutorialItem()
item['text'] = quote.css('.text::text').extract_first()
item['author'] = quote.css('.author::text').extract_first()
item['tags'] = quote.css('.tags .tag::text').extract()
yield item

next = response.css('.pager .next a::attr("href")').extract_first()
url = response.urljoin(next)
yield scrapy.Request(url=url,callback = self.parse)

启动

scrapy crawl quotes

scrapy首先会输出当前版本信息和正在启动的项目和一些配置情况

  • 抓取的数据

保存文件

  • 将结果保存为JSON文件

scrapy crawl quotes -o quotes.json

每一个Item输出一行JSON,输出后缀为jl,为jsonline的缩写:

scrapy crawl quotes -o quotes.jl

或:

scrapy crawl quotes -o quotes.jsonlines

输出格式还支持很多种,例如csv、xml、pickle、marshal等,还支持ftp、s3等远程传输,还可以通过自定义ItemExporter实现其他的输出

scrapy crawl quotes -o quotes.csv
scrapy crawl quotes -o quotes.xml
scrapy crawl quotes -o quotes.pickle
scrapy crawl quotes -o quotes.marshal
scrapy crawl quotes -o ftp://user:pass@ftp.example.com/path/to/quotes.csv

如果要输出到数据库,需要使用Item Pileline

使用Item Pipeline

负责处理爬虫从网页中抽取的内容,清洗、验证和存储数据

  • 清理HTML数据
  • 验证爬取数据,检查爬取字段
  • 查重并且丢弃重复数据
  • 将爬取的结果保存到数据库

使用方法

要实现Item Pipeline只需要定义一个类并实现process_item()方法即可。启用Item Pipeline后,Item Pipeline会自动调用这个方法。process_item方法必须返回包含数据的字典或Item对象,或者抛出DropItem异常

process_item()方法只有两个参数:

  • item:由Spider生成的Item,相当于“原材料”
  • spider:就是Spider实例

步骤:

  • 修改项目中的pipelines.py
    删除用命令行自动生成的文件内容,增加一个TextPipeline类,内容如下:

    from scrapy.exceptions import DropItem

    class TutorialPipeline:
    def __init__(self):
    # 定义限制长度为50
    self.limit = 50

    def process_item(self, item, spider):
    # 判断item的text属性是否存在,不存在则抛出DropItem异常
    if item['text']:
    # 大于50就截断然后拼接省略号,再将item返回
    if len(item['text']) > self.limit:
    item['text'] = item['text'][0:self.limit].rstrip() + '…'
    return item
    else:
    return DropItem('Missing Text')
  • 存入数据库

将处理好的item——“加工后的材料”存入MongoDB,需要定义另一个Pipeline,同样在pipelines.py里:

class MongoPipeline(object):
def __init__(self, mongo_uri, mongo_db):
self.mongo_uri = mongo_uri
self.mongo_db = mongo_db

# 使用@staticmethod或@classmethod,可以不需要实例化,直接类名.方法名()来调用
# cls作为第一个参数用来表示类本身
@classmethod
def from_crawler(cls, crawler):
# 用于获取setting中的全局配置
return cls(
mongo_uri = crawler.settings.get('MONGO_URI'),
mongo_db = crawler.settings.get('MONGO_DB')
)

# 主要是做初始化操作
def open_spider(self, spider):
self.client = pymongo.MongoClient(self.mongo_uri)
self.db = self.client[self.mongo_db]

# 核心,执行数据的插入操作
def process_item(self, item, spider):
name = item.__class__.__name__
self.db[name].insert(dict(item))
return item

# 关闭数据库连接
def close_spider(self, spider):
self.client.close()
  • 修改setting.py

添加以下内容:

# fuzhi ITEM_PIPELINES字典,键名是Pipeline的类名称,键值是调用优先级,数字越小对应的Pipeline越先调用
ITEM_PIPELINES = {
'tutoial.pipelines.TextPipeline': 300,
'tutoial.pipelines.MongoPipleline': 400,
}

# 其中<username>和<password>要修改为实际的用户名和密码
MONGO_URI = 'mongodb+srv://<username>:<password>@cluster0.yker2.mongodb.net/myFirstDatabase?retryWrites=true&w=majority'

MONGO_DB = 'tutorial'

本次爬取的数据存入的事是Mongodb数据库,用的是Mongodb提供的免费的网络集群,使用方法可以自行百度

点击CONNECT选择对应的python版本可以获得对应的MONGO_URI

将其复制并修改对应的密码即可

  • 启动爬取
    scrapy crawl quotes
    我下载了Mongodb的GUI,可以看到对应的结果为:

完整代码在Github: https://github.com/Leekinghou/tutorial

2021.4.12 面试顺利通过了,开局一张笔试题主要是问python特性:

  1. python的特点
  2. 迭代器、生成器、可迭代对象和应用场景 + 解释装饰器
  3. 数据类型,可变/不可变类型
  4. *args、**kwargs的区别
  5. 两段代码的区别
  6. 手撕排序算法
  7. 赋值、浅拷贝、深拷贝
  8. map和zip的区别
  9. 其他

面试询问:

  1. Scrapy框架的组成、数据流动流程
  2. 反爬机制、应对方法
  3. 遇到过最有难度的目标网站
  4. 对OpenCV的了解(简历中的项目)
  5. 对Mysql、redis、Mongodb的应用
  6. 对Item pipeline的了解
  7. 对事务的了解
  8. 左连接是什么?

但是只开6k,租房吃饭不剩多少钱了🤦‍♂️
不去了叭。。