scrapy框架
-
Scrapy框架五大组件
xxxxxxxxxx
121【1】引擎(Engine)----------整个框架核心
2【2】爬虫程序(Spider)------数据解析提取
3【3】调度器(Scheduler)-----维护请求队列
4【4】下载器(Downloader)----获取响应对象
5【5】管道文件(Pipeline)-----数据入库处理
6
7
8【两个中间件】
9下载器中间件(Downloader Middlewares)
10引擎->下载器,包装请求(随机代理等)
11蜘蛛中间件(Spider Middlewares)
12引擎->爬虫文件,可修改响应对象属性
-
scrapy爬虫工作流程
xxxxxxxxxx
61【1】爬虫项目启动,由引擎向爬虫程序索要第一批要爬取的URL,交给调度器去入队列
2【2】调度器处理请求后出队列,通过下载器中间件交给下载器去下载
3【3】下载器得到响应对象后,通过蜘蛛中间件交给爬虫程序
4【4】爬虫程序进行数据提取:
54.1) 数据交给管道文件去入库处理
64.2) 对于需要继续跟进的URL,再次交给调度器入队列,依次循环
-
scrapy常用命令
xxxxxxxxxx
61【1】创建爬虫项目 : scrapy startproject 项目名
2【2】创建爬虫文件
32.1) cd 项目文件夹
42.2) scrapy genspider 爬虫名 域名
5【3】运行爬虫
6scrapy crawl 爬虫名
-
scrapy项目目录结构
xxxxxxxxxx
91Baidu # 项目文件夹
2├── Baidu # 项目目录
3│ ├── items.py # 定义数据结构
4│ ├── middlewares.py # 中间件
5│ ├── pipelines.py # 数据处理
6│ ├── settings.py # 全局配置
7│ └── spiders
8│ ├── baidu.py # 爬虫文件
9└── scrapy.cfg # 项目基本配置文件
-
settings.py常用变量
xxxxxxxxxx
111【1】USER_AGENT = 'Mozilla/5.0'
2【2】ROBOTSTXT_OBEY = False
3是否遵循robots协议,一般我们一定要设置为False
4【3】CONCURRENT_REQUESTS = 32
5最大并发量,默认为16
6【4】DOWNLOAD_DELAY = 0.5
7下载延迟时间: 访问相邻页面的间隔时间,降低数据抓取的频率
8【5】COOKIES_ENABLED = False | True
9Cookie默认是禁用的,取消注释则 启用Cookie,即:True和False都是启用Cookie
10【6】DEFAULT_REQUEST_HEADERS = {}
11请求头,相当于requests.get(headers=headers)
-
创建爬虫项目步骤
xxxxxxxxxx
151【1】新建项目和爬虫文件
2scrapy startproject 项目名
3cd 项目文件夹
4新建爬虫文件 :scrapy genspider 文件名 域名
5【2】明确目标(items.py)
6【3】写爬虫程序(文件名.py)
7【4】管道文件(pipelines.py)
8【5】全局配置(settings.py)
9【6】运行爬虫
108.1) 终端: scrapy crawl 爬虫名
118.2) pycharm运行
12a> 创建run.py(和scrapy.cfg文件同目录)
13from scrapy import cmdline
14cmdline.execute('scrapy crawl maoyan'.split())
15b> 直接运行 run.py 即可
瓜子二手车直卖网 - 一级页面
-
目标
xxxxxxxxxx
91【1】抓取瓜子二手车官网二手车收据(我要买车)
2
3【2】URL地址:https://www.guazi.com/bj/buy/o{}/#bread
4URL规律: o1 o2 o3 o4 o5 ... ...
56【3】所抓数据
73.1) 汽车链接
83.2) 汽车名称
93.3) 汽车价格
实现步骤
-
步骤1 - 创建项目和爬虫文件
xxxxxxxxxx
31scrapy startproject Car
2cd Car
3scrapy genspider car www.guazi.com
-
步骤2 - 定义要爬取的数据结构
xxxxxxxxxx
81"""items.py"""
2import scrapy
3
4class CarItem(scrapy.Item):
5# 链接、名称、价格
6url = scrapy.Field()
7name = scrapy.Field()
8price = scrapy.Field()
-
步骤3 - 编写爬虫文件(代码实现1)
x
1"""
2此方法其实还是一页一页抓取,效率并没有提升,和单线程一样
3
4xpath表达式如下:
5【1】基准xpath,匹配所有汽车节点对象列表
6li_list = response.xpath('//ul[@class="carlist clearfix js-top"]/li')
7
8【2】遍历后每辆车信息的xpath表达式
9汽车链接: './a[1]/@href'
10汽车名称: './/h2[@class="t"]/text()'
11汽车价格: './/div[@class="t-price"]/p/text()'
12"""
13# -*- coding: utf-8 -*-
14import scrapy
15from ..items import CarItem
16
17
18class GuaziSpider(scrapy.Spider):
19# 爬虫名
20name = 'car'
21# 允许爬取的域名
22allowed_domains = ['www.guazi.com']
23# 初始的URL地址
24start_urls = ['https://www.guazi.com/bj/buy/o1/#bread']
25# 生成URL地址的变量
26n = 1
27
28def parse(self, response):
29# 基准xpath: 匹配所有汽车的节点对象列表
30li_list = response.xpath('//ul[@class="carlist clearfix js-top"]/li')
31# 给items.py中的 GuaziItem类 实例化
32item = CarItem()
33for li in li_list:
34item['url'] = li.xpath('./a[1]/@href').get()
35item['name'] = li.xpath('./a[1]/@title').get()
36item['price'] = li.xpath('.//div[@class="t-price"]/p/text()').get()
37
38# 把抓取的数据,传递给了管道文件 pipelines.py
39yield item
40
41# 1页数据抓取完成,生成下一页的URL地址,交给调度器入队列
42if self.n < 5:
43self.n += 1
44url = 'https://www.guazi.com/bj/buy/o{}/#bread'.format(self.n)
45# 把url交给调度器入队列
46yield scrapy.Request(url=url, callback=self.parse)
-
步骤3 - 编写爬虫文件(代码实现2)
xxxxxxxxxx
331"""
2重写start_requests()方法,效率极高
3"""
4# -*- coding: utf-8 -*-
5import scrapy
6from ..items import CarItem
7
8class GuaziSpider(scrapy.Spider):
9# 爬虫名
10name = 'car2'
11# 允许爬取的域名
12allowed_domains = ['www.guazi.com']
13# 1、去掉start_urls变量
14# 2、重写 start_requests() 方法
15def start_requests(self):
16"""生成所有要抓取的URL地址,一次性交给调度器入队列"""
17for i in range(1,6):
18url = 'https://www.guazi.com/bj/buy/o{}/#bread'.format(i)
19# scrapy.Request(): 把请求交给调度器入队列
20yield scrapy.Request(url=url,callback=self.parse)
21
22def parse(self, response):
23# 基准xpath: 匹配所有汽车的节点对象列表
24li_list = response.xpath('//ul[@class="carlist clearfix js-top"]/li')
25# 给items.py中的 GuaziItem类 实例化
26item = CarItem()
27for li in li_list:
28item['url'] = li.xpath('./a[1]/@href').get()
29item['name'] = li.xpath('./a[1]/@title').get()
30item['price'] = li.xpath('.//div[@class="t-price"]/p/text()').get()
31
32# 把抓取的数据,传递给了管道文件 pipelines.py
33yield item
-
步骤4 - 管道文件处理数据
xxxxxxxxxx
601"""
2pipelines.py处理数据
31、mysql数据库建库建表
4create database cardb charset utf8;
5use cardb;
6create table cartab(
7name varchar(200),
8price varchar(100),
9url varchar(500)
10)charset=utf8;
11"""
12# -*- coding: utf-8 -*-
13
14# 管道1 - 从终端打印输出
15class CarPipeline(object):
16def process_item(self, item, spider):
17print(dict(item))
18return item
19
20# 管道2 - 存入MySQL数据库管道
21import pymysql
22from .settings import *
23
24class CarMysqlPipeline(object):
25def open_spider(self,spider):
26"""爬虫项目启动时只执行1次,一般用于数据库连接"""
27self.db = pymysql.connect(MYSQL_HOST,MYSQL_USER,MYSQL_PWD,MYSQL_DB,charset=CHARSET)
28self.cursor = self.db.cursor()
29
30def process_item(self,item,spider):
31"""处理从爬虫文件传过来的item数据"""
32ins = 'insert into guazitab values(%s,%s,%s)'
33car_li = [item['name'],item['price'],item['url']]
34self.cursor.execute(ins,car_li)
35self.db.commit()
36
37return item
38
39def close_spider(self,spider):
40"""爬虫程序结束时只执行1次,一般用于数据库断开"""
41self.cursor.close()
42self.db.close()
43
44
45# 管道3 - 存入MongoDB管道
46import pymongo
47
48class CarMongoPipeline(object):
49def open_spider(self,spider):
50self.conn = pymongo.MongoClient(MONGO_HOST,MONGO_PORT)
51self.db = self.conn[MONGO_DB]
52self.myset = self.db[MONGO_SET]
53
54def process_item(self,item,spider):
55car_dict = {
56'name' : item['name'],
57'price': item['price'],
58'url' : item['url']
59}
60self.myset.insert_one(car_dict)
-
步骤5 - 全局配置文件(settings.py)
xxxxxxxxxx
261【1】ROBOTSTXT_OBEY = False
2【2】DOWNLOAD_DELAY = 1
3【3】COOKIES_ENABLED = False
4【4】DEFAULT_REQUEST_HEADERS = {
5"Cookie": "此处填写抓包抓取到的Cookie",
6"User-Agent": "此处填写自己的User-Agent",
7}
8
9【5】ITEM_PIPELINES = {
10'Car.pipelines.CarPipeline': 300,
11'Car.pipelines.CarMysqlPipeline': 400,
12'Car.pipelines.CarMongoPipeline': 500,
13}
14
15【6】定义MySQL相关变量
16MYSQL_HOST = 'localhost'
17MYSQL_USER = 'root'
18MYSQL_PWD = '123456'
19MYSQL_DB = 'guazidb'
20CHARSET = 'utf8'
21
22【7】定义MongoDB相关变量
23MONGO_HOST = 'localhost'
24MONGO_PORT = 27017
25MONGO_DB = 'guazidb'
26MONGO_SET = 'guaziset'
-
步骤6 - 运行爬虫(run.py)
xxxxxxxxxx
31"""run.py"""
2from scrapy import cmdline
3cmdline.execute('scrapy crawl car'.split())
知识点汇总
-
数据持久化 - 数据库
xxxxxxxxxx
151【1】在setting.py中定义相关变量
2【2】pipelines.py中导入settings模块
3def open_spider(self,spider):
4"""爬虫开始执行1次,用于数据库连接"""
56def process_item(self,item,spider):
7"""具体处理数据"""
8return item
910def close_spider(self,spider):
11"""爬虫结束时执行1次,用于断开数据库连接"""
12【3】settings.py中添加此管道
13ITEM_PIPELINES = {'':200}
14
15【注意】 :process_item() 函数中一定要 return item ,当前管道的process_item()的返回值会作为下一个管道 process_item()的参数
-
数据持久化 - csv、json文件
xxxxxxxxxx
81【1】存入csv文件
2scrapy crawl car -o car.csv
34【2】存入json文件
5scrapy crawl car -o car.json
6
7【3】注意: settings.py中设置导出编码 - 主要针对json文件
8FEED_EXPORT_ENCODING = 'utf-8'
-
节点对象.xpath('')
xxxxxxxxxx
71【1】列表,元素为选择器 @
2[
3<selector xpath='xxx' data='A'>,
4<selector xpath='xxx' data='B'>
5]
6【2】列表.extract() :序列化列表中所有选择器为Unicode字符串 ['A','B']
7【3】列表.extract_first() 或者 get() :获取列表中第1个序列化的元素(字符串) 'A'
-
课堂练习
xxxxxxxxxx
11【熟悉整个流程】 : 将猫眼电影案例数据抓取,存入MySQL数据库
瓜子二手车直卖网 - 二级页面
-
目标说明
xxxxxxxxxx
91【1】在抓取一级页面的代码基础上升级
2【2】一级页面所抓取数据(和之前一样):
32.1) 汽车链接
42.2) 汽车名称
52.3) 汽车价格
6【3】二级页面所抓取数据
73.1) 行驶里程: //ul[@class="assort clearfix"]/li[2]/span/text()
83.2) 排量: //ul[@class="assort clearfix"]/li[3]/span/text()
93.3) 变速箱: //ul[@class="assort clearfix"]/li[4]/span/text()
在原有项目基础上实现
-
步骤1 - items.py
xxxxxxxxxx
151# 添加二级页面所需抓取的数据结构
2
3import scrapy
4
5class GuaziItem(scrapy.Item):
6# define the fields for your item here like:
7# 一级页面: 链接、名称、价格
8url = scrapy.Field()
9name = scrapy.Field()
10price = scrapy.Field()
11# 二级页面: 时间、里程、排量、变速箱
12time = scrapy.Field()
13km = scrapy.Field()
14disp = scrapy.Field()
15trans = scrapy.Field()
-
步骤2 - car2.py
xxxxxxxxxx
431"""
2重写start_requests()方法,效率极高
3"""
4# -*- coding: utf-8 -*-
5import scrapy
6from ..items import CarItem
7
8class GuaziSpider(scrapy.Spider):
9# 爬虫名
10name = 'car2'
11# 允许爬取的域名
12allowed_domains = ['www.guazi.com']
13# 1、去掉start_urls变量
14# 2、重写 start_requests() 方法
15def start_requests(self):
16"""生成所有要抓取的URL地址,一次性交给调度器入队列"""
17for i in range(1,6):
18url = 'https://www.guazi.com/bj/buy/o{}/#bread'.format(i)
19# scrapy.Request(): 把请求交给调度器入队列
20yield scrapy.Request(url=url,callback=self.parse)
21
22def parse(self, response):
23# 基准xpath: 匹配所有汽车的节点对象列表
24li_list = response.xpath('//ul[@class="carlist clearfix js-top"]/li')
25# 给items.py中的 GuaziItem类 实例化
26item = CarItem()
27for li in li_list:
28item['url'] = 'https://www.guazi.com' + li.xpath('./a[1]/@href').get()
29item['name'] = li.xpath('./a[1]/@title').get()
30item['price'] = li.xpath('.//div[@class="t-price"]/p/text()').get()
31# Request()中meta参数: 在不同解析函数之间传递数据,item数据会随着response一起返回
32yield scrapy.Request(url=item['url'], meta={'meta_1': item}, callback=self.detail_parse)
33
34def detail_parse(self, response):
35"""汽车详情页的解析函数"""
36# 获取上个解析函数传递过来的 meta 数据
37item = response.meta['meta_1']
38item['km'] = response.xpath('//ul[@class="assort clearfix"]/li[2]/span/text()').get()
39item['disp'] = response.xpath('//ul[@class="assort clearfix"]/li[3]/span/text()').get()
40item['trans'] = response.xpath('//ul[@class="assort clearfix"]/li[4]/span/text()').get()
41
42# 1条数据最终提取全部完成,交给管道文件处理
43yield item
-
步骤3 - pipelines.py
xxxxxxxxxx
151# 将数据存入mongodb数据库,此处我们就不对MySQL表字段进行操作了,如有兴趣可自行完善
2# MongoDB管道
3import pymongo
4
5class GuaziMongoPipeline(object):
6def open_spider(self,spider):
7"""爬虫项目启动时只执行1次,用于连接MongoDB数据库"""
8self.conn = pymongo.MongoClient(MONGO_HOST,MONGO_PORT)
9self.db = self.conn[MONGO_DB]
10self.myset = self.db[MONGO_SET]
11
12def process_item(self,item,spider):
13car_dict = dict(item)
14self.myset.insert_one(car_dict)
15return item
-
步骤4 - settings.py
xxxxxxxxxx
51# 定义MongoDB相关变量
2MONGO_HOST = 'localhost'
3MONGO_PORT = 27017
4MONGO_DB = 'guazidb'
5MONGO_SET = 'guaziset'
盗墓笔记小说抓取 - 三级页面
-
目标
xxxxxxxxxx
41【1】URL地址 :http://www.daomubiji.com/
2【2】要求 : 抓取目标网站中盗墓笔记所有章节的所有小说的具体内容,保存到本地文件
3./data/novel/盗墓笔记1:七星鲁王宫/七星鲁王_第一章_血尸.txt
4./data/novel/盗墓笔记1:七星鲁王宫/七星鲁王_第二章_五十年后.txt
-
准备工作xpath
xxxxxxxxxx
131【1】一级页面 - 大章节标题、链接:
21.1) 基准xpath匹配a节点对象列表: '//li[contains(@id,"menu-item-20")]/a'
31.2) 大章节标题: './text()'
41.3) 大章节链接: './@href'
56【2】二级页面 - 小章节标题、链接
72.1) 基准xpath匹配article节点对象列表: '//article'
82.2) 小章节标题: './a/text()'
92.3) 小章节链接: './a/@href'
1011【3】三级页面 - 小说内容
123.1) p节点列表: '//article[@class="article-content"]/p/text()'
133.2) 利用join()进行拼接: ' '.join(['p1','p2','p3',''])
项目实现
-
1、创建项目及爬虫文件
xxxxxxxxxx
31scrapy startproject Daomu
2cd Daomu
3scrapy genspider daomu www.daomubiji.com
-
2、定义要爬取的数据结构 - itemspy
xxxxxxxxxx
61class DaomuItem(scrapy.Item):
2# 拷问: 你的pipelines.py中需要处理哪些数据? 文件名、路径
3# 文件名:小标题名称 son_title: 七星鲁王 第一章 血尸
4son_title = scrapy.Field()
5directory = scrapy.Field()
6content = scrapy.Field()
-
3、爬虫文件实现数据抓取 - daomu.py
xxxxxxxxxx
511# -*- coding: utf-8 -*-
2import scrapy
3from ..items import DaomuItem
4import os
5
6class DaomuSpider(scrapy.Spider):
7name = 'daomu'
8allowed_domains = ['www.daomubiji.com']
9start_urls = ['http://www.daomubiji.com/']
10
11def parse(self, response):
12"""一级页面解析函数:提取大标题+大链接,并把大链接交给调度器入队列"""
13a_list = response.xpath('//li[contains(@id,"menu-item-20")]/a')
14for a in a_list:
15item = DaomuItem()
16parent_title = a.xpath('./text()').get()
17parent_url = a.xpath('./@href').get()
18item['directory'] = './novel/{}/'.format(parent_title)
19# 创建对应文件夹
20if not os.path.exists(item['directory']):
21os.makedirs(item['directory'])
22# 交给调度器入队列
23yield scrapy.Request(url=parent_url, meta={'meta_1':item}, callback=self.detail_page)
24
25# 返回了11个response,调用了这个函数
26def detail_page(self, response):
27"""二级页面解析函数:提取小标题、小链接"""
28# 把item接收
29meta_1 = response.meta['meta_1']
30art_list = response.xpath('//article')
31for art in art_list:
32# 只要有继续交往调度器的请求,就必须新建item对象
33item = DaomuItem()
34item['son_title'] = art.xpath('./a/text()').get()
35son_url = art.xpath('./a/@href').get()
36item['directory'] = meta_1['directory']
37# 再次交给调度器入队列
38yield scrapy.Request(url=son_url, meta={'item':item}, callback=self.get_content)
39
40# 盗墓笔记1: 传过来了75个response
41# 盗墓笔记2: 传过来了 n 个response
42# ... ...
43def get_content(self, response):
44"""三级页面解析函数:提取具体小说内容"""
45item = response.meta['item']
46# content_list: ['段落1','段落2','段落3',...]
47content_list = response.xpath('//article[@class="article-content"]/p/text()').extract()
48item['content'] = ' '.join(content_list)
49
50# 至此,一条item数据全部提取完成
51yield item
-
4、管道文件实现数据处理 - pipelines.py
xxxxxxxxxx
81class DaomuPipeline(object):
2def process_item(self, item, spider):
3# filename: ./novel/盗墓笔记1:七星鲁王宫/七星鲁王_第一章_血尸.txt
4filename = '{}{}.txt'.format(item['directory'], item['son_title'].replace(' ', '_'))
5with open(filename, 'w') as f:
6f.write(item['content'])
7
8return item
-
5、全局配置 - setting.py
xxxxxxxxxx
101ROBOTSTXT_OBEY = False
2DOWNLOAD_DELAY = 0.5
3DEFAULT_REQUEST_HEADERS = {
4'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
5'Accept-Language': 'en',
6'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36'
7}
8ITEM_PIPELINES = {
9'Daomu.pipelines.DaomuPipeline': 300,
10}
今日作业
xxxxxxxxxx
9
1
【1】腾讯招聘职位信息抓取(二级页面)
2
要求:输入职位关键字,抓取该类别下所有职位信息(到职位详情页抓取)
3
具体数据如下:
4
1.1) 职位名称
5
1.2) 职位地点
6
1.3) 职位类别
7
1.4) 发布时间
8
1.5) 工作职责
9
1.6) 工作要求