五、Scrapy
1. 初识Scrapy
1.1 Scrapy是什么
Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。
1.2 安装Scrapy
(1)在pycharm中安装
(2)使用anaconda
1.3 Scrapy的项目
(1)在终端创建Scrapy项目
eg: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 爬虫名字 网页的域名
(3)爬虫文件的基本组成:
继承scrapy.Spider类
name = ‘baidu’ --> 运行爬虫文件时使用的名字
allowed_domains --> 爬虫允许的域名,在爬取的时候,如果不是此域名之下的url
会被过滤掉
start_urls --> 声明了爬虫的起始地址,可以写多个url,一般是一个
parse(self, response)
--> 解析数据的回调函数
response.text --> 响应的是字符串
response.body --> 响应的是二进制文件
response.xpath() --> xpath方法的返回值类型是selector列表
extract() --> 提取的是selector对象的是data
extract_first --> 提取的是selector列表中的第一个数据
(4)运行爬虫文件:
scrapy crawl 爬虫名称
注意:应在spiders文件夹内执行
2. Scrapy 的应用
2.1 Scrapy Shell
(1)什么是scrapy shell
Scrapy终端,是一个交互终端,供您在未启动spider的情况下尝试及调试您的爬取代码。 其本意是用来测试提取数据的代码,不过您可以将其作为正常的Python终端,在上面测试任何的Python代码。
该终端是用来测试XPath或CSS表达式,查看他们的工作方式及从爬取的网页中提取的数据。 在编写您的spider时,该 终端提供了交互性测试您的表达式代码的功能,免去了每次修改后运行spider的麻烦。
一旦熟悉了Scrapy终端后,您会发现其在开发和调试spider时发挥的巨大作用。
语法:
(1)response对象:
response.body
response.text
response.url
response.status
(2)response的解析:
response.xpath() (常用)
使用xpath路径查询特定元素,返回一个selector列表对象
response.css()
使用css_selector查询元素,返回一个selector列表对象
获取内容 :response.css('#su::text').extract_first()
获取属性 :response.css('#su::attr(“value”)').extract_first()
(3)selector对象(通过xpath方法调用返回的是seletor列表)
extract()
提取selector对象的值
如果提取不到值 那么会报错
使用xpath请求到的对象是一个selector对象,需要进一步使用extract()方法拆 包,转换为unicode字符串
extract_first()
提取seletor列表中的第一个值
如果提取不到值 会返回一个空值
返回第一个解析到的值,如果列表为空,此种方法也不会报错,会返回一个空值
xpath()
css()
注意:每一个selector对象可以再次的去使用xpath或者css方法
2.2 yield
(1)带有yield的函数不再是一个普通的函数,而是一个生成器generator,可用于迭代
(2)yield是一个类似return的关键字,迭代一次遇到yield时就返回yield后面(右边)的值。重点是:下一次迭代时,从上一次迭代遇到的yield后面的代码(下一行)开始执行。
(3)简要理解:yield就是return返回一个值,并且记住这个返回的位置,下次迭代就从这个位置后(下一行)开始案例:
1.当当网 (1)yield(2).管道封装(3).多条管道下载 (4)多页数据下载
2.电影天堂 (1)一个item包含多级页面的数据
2.3 CrawlSpider
1.继承自scrapy.Spider
2.独门秘笈
CrawlSpider可以定义规则,再解析html内容的时候,可以根据链接规则提取出指定的链接,然后再向这些链接发 送请求所以,如果有需要跟进链接的需求,意思就是爬取了网页之后,需要提取链接再次爬取,使用CrawlSpider是非常合适的 。
3.提取链接
链接提取器,在这里就可以写规则提取指定链接
scrapy.linkextractors.LinkExtractor(
allow = (), # 正则表达式 提取符合正则的链接
deny = (), # (不用)正则表达式 不提取符合正则的链接
allow_domains = (), # (不用)允许的域名
deny_domains = (), # (不用)不允许的域名
restrict_xpaths = (), # xpath,提取符合xpath规则的链接
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 【注‐
‐稍后看】follow=true 是否跟进 就是按照提取连接规则进行提取
7.CrawlSpider案例
需求:读书网数据入库
1.创建项目:scrapy startproject dushuproject
2.跳转到spiders路径 cd\dushuproject\dushuproject\spiders
3.创建爬虫类:scrapy genspider ‐t crawl read www.dushu.com
4.items
5.spiders
6.settings
7.pipelines数据保存到本地 数据保存到mysql数据库
2.4 数据入库
(1)settings配置参数:
DB_HOST = '192.168.231.128'
DB_PORT = 3306 DB_USER = 'root'
DB_PASSWORD = '1234'
DB_NAME = 'test'
DB_CHARSET = 'utf8'
(2)管道配置
from scrapy.utils.project
import get_project_settings
import pymysql
class MysqlPipeline(object):
#__init__方法和open_spider的作用是一样的
#init是获取settings中的连接参数
def __init__(self):
settings = get_project_settings()
self.host = settings['DB_HOST']
self.port = settings['DB_PORT']
self.user = settings['DB_USER']
self.pwd = settings['DB_PWD']
self.name = settings['DB_NAME']
self.charset = settings['DB_CHARSET']
self.connect()
# 连接数据库并且获取cursor对象
def connect(self):
self.conn = pymysql.connect(host=self.host,
port=self.port,
user=self.user,
password=self.pwd,
db=self.name,
charset=self.charset)
self.cursor = self.conn.cursor()
def process_item(self, item, spider):
sql = 'insert into book(image_url, book_name, author, info) values("%s", "%s", "%s", "%s")' % (item['image_url'], item['book_name'], item['author'], item['info'])
sql = 'insert into book(image_url,book_name,author,info) values ("{}","{}","{}","{}")'.format(item['image_url'], item['book_name'], item['author'], item['info'])
# 执行sql语句
self.cursor.execute(sql)
self.conn.commit()
return item
2.5 日志信息和日志等级
(1)日志级别:
CRITICAL:严重错误
ERROR: 一般错误
WARNING: 警告
INFO: 一般信息
DEBUG: 调试信息
默认的日志等级是DEBUG
只要出现了DEBUG或者DEBUG以上等级的日志
那么这些日志将会打印
(2)settings.py文件设置:
默认的级别为DEBUG,会显示上面所有的信息
在配置文件中 settings.py
LOG_FILE : 将屏幕显示的信息全部记录到文件中,屏幕不再显示,注意文件后缀一定是.log
LOG_LEVEL : 设置日志显示的等级,就是显示哪些,不显示哪些
2.6 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所携带的数据,这是一个字典
2.7 代理
(1)到settings.py中,打开一个选项
DOWNLOADER_MIDDLEWARES = { 'postproject.middlewares.Proxy': 543, }
(2)到middlewares.py中写代码
def process_request(self, request, spider):
request.meta['proxy'] = 'https://113.68.202.10:9999'
return None
3. Scrapy的使用案例
3.1 Scrapy的基本框架
Scrapy创建的项目和爬虫文件以及运行爬虫代码都是在控制台(可以使用pycharm中的控制台)进行,当然,编写具体的功能需要在爬虫文件中。
1. scrapy 项目的结构
项目名字
项目文件
spiders文件夹 (存储的是爬虫文件)
init
自定义的爬虫文件 核心功能文件****
init
items 定义数据结构的地方 爬取的数据都包含哪些
middleware 中间件 代理
pipelines 管道 用来处理下载的数据
settings 匹配文件 robots协议 ua定义等
创建一个scrapy项目的步骤
(1)创建爬虫的项目
scrapy startproject 项目的名字
注意:项目的名字不允许使用数字开头,也不能包含中文;
每个爬虫程序都需要单独创建一个项目文件;
(2)创建爬虫文件
要在 spiders文件夹中去创建爬虫文件
进入spiders文件夹
cd 项目的名字\项目的名字\spiders
eg: cd scrapy_baidu\scrapy_baidu\spiders
创建爬虫文件
scrapy genspider 爬虫文件的名字 要爬取的网页
eg:scrapy genspider baidu http://www.baidu.com
注意:一般情况下不需要添加http协议,因为start urls的值是根据allowed domains修改的,所以添加了http的话,那么start urls就多了个http,还需要我们去手动删除。
(3)运行爬虫代码
scrapy crawl 爬虫的名字
eg:scrapy crawl baidu
爬虫文件内的框架
即文件 百度.py 里的代码
import scrapy
class BaiduSpider(scrapy.spider):
# 爬虫的名字,用于运行爬虫的时候使用的值
name = "baidu"
# 允许访问的域名
allowed_domains = ["www.baidu.com"]
# 起始的url地址
# start_urls 是 在 allowed_domains 的前面加了 http://
# 在 allowed_domains 的后面加了 /
start_urls = ["http://www.baidu.com/"]
# 执行了strat_urls 之后执行的方法 方法中的response就是返回的那个对象
# 相当于response = urllib.requests.urlopen()
# response = requests.get()
def parse(self, response):
print("helloworld")
3.2 Scrapy的Xpath使用
以 汽车之家 网站为例 https://car.autohome.com.cn/price/brand-15.html
爬虫文件内容
import scrapy
# 注意:如果请求的接口是以html结尾的不需要加 /
class CarSpider(scrapy.Spider):
name = "car"
allowed_domains = ["https://car.autohome.com.cn/price/brand-15.html"]
start_urls = ["https://car.autohome.com.cn/price/brand-15.html"]
def parse(self, response):
print('============================')
name_list = response.xpath('//div[@class="main-title"]/a/text()')
price_list = response.xpath('//span[@class="lever-price red"]/span/text()')
for i in range(len(name_list)):
name = name_list[i].extract()
price = price_list[i].extract()
print(name, price)
3.3 Scrapy使用pipeline模块下载爬取的数据
下载文件和图片
Scrapy为下载item中包含的文件(比如再爬取到产品时,同时也想保存到对应图片)提供了一个可重用的item pipeline。这些pipeline有些共同的方法和结构(我们称之为media Pipeline),一般来说我们会使用到File Pipeline和Images Pipeline。
使用Scrapy框架内置方法的好处
我们为什么要选择用Scrapy内置的下载文件的防范:
避免重复下载最近已经下载过的数据。
可以方便的指定文件存储的路径。
可以将下载的图片转存成通用的格式,比如png或jpg
可以方便的生成缩略图。
可以方便的检测图片的宽和高,确保他们满足最小限制。
异步下载,效率非常高。
下载文件的File Pipeline
当使用File Pipeline下载文件的时候,按照以下步骤来完成:
定义好一个Item,然后再这个Item中定义两个属性,分别为file_urls以及files。file_urls是用来存储需要下载的文件的url链接,需要给一个列表。
当文件下载完成后,会把文件下载相关的信息存储到item的files属性中。比如下载路径、下载的url和文件的校验码等。
在配置文件setting.py中配置FILES_STORE,这个配置时用来设置文件下载下来的路径。
启动pipeline:在ITEM_PIPELINES中设置scrapy.pipelines.files.FilesPipeline:1。
传统下载方式
传统的下载方式在这里就简单说下步骤:
使用scrapy命令创建项目;
使用命令创建爬虫;
更改setting.py文件配置信息;
编写items.py代码;
编写spider模块下代码(爬取网页中需要下载的图片链接);
编写pipelines.py代码(进行数据处理,图片的保存等)。
案例如下:
以当当网为例 http://category.dangdang.com/cp01.01.02.00.00.00.html
首先需要再scrapy项目下的 item.py 文件里定义你需要下载的东西
pipelines.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
import urllib.request
# useful for handling different item types with a single interface
from itemadapter import ItemAdapter
# 如果想使用管道的话就必须在settings中开启管道
class ScrapyDangdang39Pipeline:
# 在爬虫文件开始之前就执行的一个方法
def open_spider(self, spider):
self.fp = open('book.json', 'w', encoding='utf-8')
# 参数item就是yield后面的book对象
def process_item(self, item, spider):
# 以下这种模式不推荐,因为每传递一个对象就打开一次文件,对文件的操作过于频繁
# write方法必须需要写一个字符串,而不能是其他的对象
# w模式 会每一个对象都打开一次文件 覆盖之前的内容
# a模式 追加
# with open('book.json', 'a', encoding='utf-8') as fp:
# fp.write(str(item))
self.fp.write(str(item))
return item
# 在爬虫文件执行完之后再执行的方法
def close_spider(self, spider):
self.fp.close()
# 多条管道开启
# 定义管道类
# 在settings中开启管道
# 'scrapy_dangdang_39.pipelines.DangDangDownloadPipeline"': 301
class DangDangDownloadPipeline:
def process_item(self, item, spider):
url = 'http:' + item.get('src')
filename = './books/' + item.get('name') + '.jpg'
urllib.request.urlretrieve(url=url, filename=filename)
return 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 ScrapyDangdang39Item(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
#图片
src = scrapy.Field()
# 名字
name = scrapy.Field()
# 价格
price = scrapy.Field()
配置文件中启动pipeline
:在ITEM_PIPELINES
中设置scrapy.pipelines.images.ImagesPipeline:1
。修改代码如下:
ITEM_PIPELINES = {
# 管道可以有很多个 管道是有优先值的 优先值的范围是1到1000 值越小优先级越高
"scrapy_dangdang_39.pipelines.ScrapyDangdang39Pipeline": 300,
# DangDangDownloadPipeline
'scrapy_dangdang_39.pipelines.DangDangDownloadPipeline': 301
}
spiders文件夹下的dang.py代码如下:
import scrapy
# 编译器问题所以会报错,下面用到时不会报错
from scrapy_dangdang_39.items import ScrapyDangdang39Item
class DangSpider(scrapy.Spider):
name = "dang"
# 如果是多页下载的话 需要调整allowed_domains的范围 一般情况下只写域名
allowed_domains = ["category.dangdang.com"] # 允许在哪些域名下的url
start_urls = ["http://category.dangdang.com/cp01.01.02.00.00.00.html"] # 起始爬取的url
base_url = 'http://category.dangdang.com/pg'
page = 1
def parse(self, response):
# src = //ul[@id="component_59"]/li//img/@src
# alt = //ul[@id="component_59"]/li//img/@alt
# price = //ul[@id="component_59"]/li//p[@class="price"]/span[1]/text()
# 所有的selector的对象 都可以再次调用xpath方法
li_list = response.xpath('//ul[@id="component_59"]/li')
for li in li_list:
# 网页做了懒加载所以把@src 换成 @data-original
# 第一张图片和其他图片的标签属性是不一样的
# 第一张图片的src是可以使用的,其他的图片地址是@data-original
src = li.xpath('.//img/@data-original').extract_first()
if src: #若拿到的数据不为空(None)
src = src
else:
src = li.xpath('.//img/@src').extract_first() # 若为空则拿src的
name = li.xpath('.//img/@alt').extract_first()
price = li.xpath('.//p[@class="price"]/span[1]/text()').extract_first()
book = ScrapyDangdang39Item(src=src, name=name, price=price)
# yield 就是return一个值
# 获取一个book就将book交给pipelines
yield book
# http://category.dangdang.com/cp01.01.02.00.00.00.html
# http://category.dangdang.com/pg2-cp01.01.02.00.00.00.html
if self.page < 100:
self.page = self.page + 1
url = self.base_url + str(self.page) + '-cp01.01.02.00.00.00.html'
print(url)
# 怎么去调用parse方法
# scrapy.Request 就是 scrapy 的get请求
# url就是请求地址
# callback是你要执行的那个函数,注意不需要加()
yield scrapy.Request(url=url, callback=self.parse)
3.4 Scrapy的post请求
还是以百度翻译为例
spiders文件夹下的testpost.py代码如下:
import scrapy
import json
class TestpostSpider(scrapy.Spider):
name = "testpost"
allowed_domains = ["https://fanyi.baidu.com/sug"]
# post请求如果没有参数,那么这个请求没有任何意义
# 所以start_urls也没有用了
# 所以parse方法也没用了
# start_urls = ["http://www.baidu.com/"]
#
# def parse(self, response):
# pass
def start_requests(self):
url = 'https://fanyi.baidu.com/sug'
data = {
'kw': 'final'
}
yield scrapy.FormRequest(url=url, formdata=data, callback=self.parse_second)
def parse_second(self, response):
content = response.text
obj = json.loads(content) # encoding 去掉。
print(obj)
3.5 Scrapy的CrawlSpider的使用
以读书网下载书名和图片并保存到mysql数据库中为例 https://www.dushu.com/book/1188_1.html
pipelines.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
from itemadapter import ItemAdapter
from scrapy.utils.project import get_project_settings
class ScrapyReadbook41Pipeline:
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()
from scrapy.utils.project import get_project_settings
import pymysql
class MysqlPipeline:
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()
item.py代码如下:
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html
import scrapy
class ScrapyReadbook41Item(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
name = scrapy.Field()
src = scrapy.Field()
配置文件settings代码修改如下:
# Configure pymysql
DB_HOST = 'localhost' # 本地数据库为 localhost 远程数据库为 ip地址
# port 端口号是一个整数
DB_PORT = 3306
DB_USER = 'root'
DB_PASSWORD = 'hadoop'
DB_NAME = 'spider01'
DB_CHARSET = 'utf8'
# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
"scrapy_readbook_41.pipelines.ScrapyReadbook41Pipeline": 300,
# MysqlPipeline
'scrapy_readbook_41.pipelines.MysqlPipeline': 301,
}
spiders文件夹下的read.py代码如下:
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from scrapy_readbook_41.items import ScrapyReadbook41Item
# CrawlSpider为全站爬取而生
class ReadSpider(CrawlSpider):
name = "read"
allowed_domains = ["www.dushu.com"]
start_urls = ["https://www.dushu.com/book/1188_1.html"]
rules = (
Rule(LinkExtractor(allow=r"/book/1188_\d+\.html"),
callback="parse_item",
follow=True),)
def parse_item(self, response):
img_list = response.xpath('//div[@class="bookslist"]//img')
for img in img_list:
name = img.xpath('./@alt').extract_first()
src = img.xpath('./@data-original').extract_first()
book = ScrapyReadbook41Item(name=name, src=src)
yield book
# 实操中出现了问题,只下载了每页的最后一个数据,且第一页每页下载图片链接
# 解决: yield book 未缩进