序言
1. 内容介绍
本章详细介绍了Scrapy框架的安装,通过Scrapy框架架构,讲解了Scrapy各个组件的作用。
2. 理论目标
- 掌握Scrapy安装
- 了解Scrapy组件结构和工作流程
3. 实践目标
- 实现一个简单爬虫项目
4. 实践案例
无
5. 内容目录
-
1.Scrapy 简介与安装
-
2.Scrapy 项目创建
-
3.Scrapy 进阶
第1节 Scrapy 简介与安装
1. Scrapy 简介
Scrapy是用python实现的一个为了爬取网站数据,提取结构性数据而编写的应用框架;用于抓取web站点并从页面中提取结构化的数据,用于数据挖掘、监测和自动化测试。
2. Scrapy 说明
架构组件构成
Scrapy框架主要由五大组件组成,它们分别是:
调度器(Scheduler)、下载器(Downloader)、爬虫(Spider)和实体管道(Item Pipeline)、Scrapy引擎(Scrapy Engine)。
下面我们分别介绍各个组件的作用。
组件 | 作用说明 | |
---|---|---|
Spiders | 爬虫脚本 | 定义了爬取的逻辑和网页内容的解析规则,主要负责解析响应并生成结果和新的请求 |
Engine | 引擎 | 处理整个系统的数据流处理,出发事物,框架的核心。 |
Scheduler | 调度器 | 接受引擎发过来的请求,并将其加入队列中,在引擎再次请求时将请求提供给引擎 |
Downloader | 下载器 | 下载网页内容,并将下载内容返回给spider |
ItemPipeline | 项目管道 | 负责处理spider从网页中抽取的数据,主要是负责清洗,验证和向数据库中存储数据 |
Downloader Middlewares | 下载中间件 | 是处于Scrapy的Request和Requesponse之间的处理模块 |
Spider Middlewares | spider中间件 | 位于引擎和spider之间的框架,主要处理spider输入的响应和输出的结果及新的请求middlewares.py里实现 |
架构组件关系
流程解析
- spiders的yeild将request发送给engin
- engine对request不做任何处理发送给scheduler
- scheduler( url调度器),生成request交给engin
- engine拿到request,通过middleware进行层层过滤发送给downloader
- downloader在网上获取到response数据之后,又经过middleware进行层层过滤发送给engin
- engine获取到response数据之后,返回给spiders,spiders的parse()方法对获取到的response数据进行处理,解析出items或者requests
- 将解析出来的items或者requests(下一个请求)发送给engin
- engin获取到items或者requests,将items发送给itempipelines,将requests发送给scheduler
3. Scrapy 安装
安装语法:pip install scrapy -i https://pypi.tuna.tsinghua.edu.cn/simple/
查看版本语法:scrapy version
第2节 Scrapy 项目创建
1. 创建爬虫项目
命令:
scrapy startproject scrapytest
文件作用说明
- 目录 spider 放置爬虫代码,放置目标url、response解析
- 文件 items.py 用于保存所抓取的数据的容器,其存储方式类似于 Python 的字典
- 文件 middlewares.py 提供一种简便的机制,通过允许插入自定义代码来拓展 Scrapy 的功能(少用)
- 文件 pipelines.py 用于把解析好的数据 items 输出到数据库或指定文件
- 文件 settings.py 用于配置爬虫信息(如头部信息、并行数、优先级等)
- 文件 scrapy.cfg 用于项目级别的配置(少用)
2. 创建爬虫应用
进入项目目录 cd scrapytest
创建爬虫 scrapy genspider douban “https://movie.douban.com/top250”
3. 配置爬虫信息
获取头部信息
配置头部信息
文件:settings.py
- 填写头部信息
- ROBOTSTXT_OBEY 设置为False,即不遵守robots.txt,否则好多网站都爬不到
配置爬虫脚本
文件:douban.py
- 检查 start_urls 的值是否目标网站url,因为 Scrapy 有可能会进行修改
- 删除 allowed_domains,此处为限制爬取的范围,最好注释
import scrapy class DoubanSpider(scrapy.Spider): name = 'douban' # allowed_domains = ['https://movie.douban.com/top250'] start_urls = ['https://movie.douban.com/top250'] movieN = 1 def parse(self, response): pass
4. 提取爬虫信息
页面分析
网页模型按盒子模型组成
正在上传…重新上传取消
分析页面模块层级、元素构成
提取路径测试
启动shell测试:scrapy shell https://movie.douban.com/top250 下载目标网站页面
查看下载到的页面内容
获取xpath 路径
获取到的xpath路径:
/html/body/div[3]/div[1]/div/div[1]/ol/li[1]/div/div[2]/div[1]/a
根据xpath 获取文本语法,在后面补充成:
/html/body/div[3]/div[1]/div/div[1]/ol/li[1]/div/div[2]/div[1]/a//text()
cmd 窗口获取相应信息语句:
response.xpath("/html/body/div[3]/div[1]/div/div[1]/ol/li[1]/div/div[2]/div[1]/a//text()").getall()
依次类推,获取所有信息路径:
- 电影名称:/html/body/div[3]/div[1]/div/div[1]/ol/li[1]/div/div[2]/div[1]/a
- 电影创作人员:/html/body/div[3]/div[1]/div/div[1]/ol/li[1]/div/div[2]/div[2]/p[1]
- 电影年份、国家、类型:/html/body/div[3]/div[1]/div/div[1]/ol/li[1]/div/div[2]/div[2]/p[1]
- 电影评价-星数:/html/body/div[3]/div[1]/div/div[1]/ol/li[4]/div/div[2]/div[2]/div/span[1]
- 电影评价-评分:/html/body/div[3]/div[1]/div/div[1]/ol/li[4]/div/div[2]/div[2]/div/span[2]
- 电影评价-评价数:/html/body/div[3]/div[1]/div/div[1]/ol/li[4]/div/div[2]/div[2]/div/span[4]
- 电影介绍:/html/body/div[3]/div[1]/div/div[1]/ol/li[3]/div/div[2]/div[2]/p[2]/span
完成测试,根据xpath 语法更新如下:
- 电影名称
name = response.xpath("/html/body/div[3]/div[1]/div/div[1]/ol/li[1]/div/div[2]/div[1]/a//text()").getall()
- 电影创作人员
movieMakers = response.xpath("/html/body/div[3]/div[1]/div/div[1]/ol/li[1]/div/div[2]/div[2]/p[1]/text()[1]").getall()
- 电影年份、国家、类型
baseInfo = response.xpath("/html/body/div[3]/div[1]/div/div[1]/ol/li[1]/div/div[2]/div[2]/p[1]/text()[2]").getall()
- 电影评价
star = response.xpath("/html/body/div[3]/div[1]/div/div[1]/ol/li[4]/div/div[2]/div[2]/div/span[1]/@class").getall() score = response.xpath("/html/body/div[3]/div[1]/div/div[1]/ol/li[4]/div/div[2]/div[2]/div/span[2]/text()").getall() comments = response.xpath("/html/body/div[3]/div[1]/div/div[1]/ol/li[4]/div/div[2]/div[2]/div/span[4]/text()").getall()
- 电影介绍
desc = response.xpath("/html/body/div[3]/div[1]/div/div[1]/ol/li[3]/div/div[2]/div[2]/p[2]/span/text()").getall()
更新爬虫文件
将测试通过的信息提取代码更新到 douban.py,并通过print()打印出来
import scrapy class DoubanSpider(scrapy.Spider): name = 'douban' start_urls = ['https://movie.douban.com/top250'] movieN = 1 def parse(self, response): ## 电影名称 name = response.xpath("/html/body/div[3]/div[1]/div/div[1]/ol/li[1]/div/div[2]/div[1]/a//text()").getall() ## 电影创作人员 movieMakers = response.xpath("/html/body/div[3]/div[1]/div/div[1]/ol/li[1]/div/div[2]/div[2]/p[1]/text()[1]").getall() ## 电影年份、国家、类型 baseInfo = response.xpath("/html/body/div[3]/div[1]/div/div[1]/ol/li[1]/div/div[2]/div[2]/p[1]/text()[2]").getall() ## 电影评价 star = response.xpath("/html/body/div[3]/div[1]/div/div[1]/ol/li[4]/div/div[2]/div[2]/div/span[1]/@class").getall() score = response.xpath("/html/body/div[3]/div[1]/div/div[1]/ol/li[4]/div/div[2]/div[2]/div/span[2]/text()").getall() comments = response.xpath("/html/body/div[3]/div[1]/div/div[1]/ol/li[4]/div/div[2]/div[2]/div/span[4]/text()").getall() ## 电影介绍 desc = response.xpath("/html/body/div[3]/div[1]/div/div[1]/ol/li[3]/div/div[2]/div[2]/p[2]/span/text()").getall() ## 打印电影信息 print(name, movieMakers, baseInfo, star, score, comments, desc)
尝试启动爬虫
scrapy crawl douban
终端尝试启动爬虫,观察输出结果
爬虫脚本优化
优化1
整理信息格式,douban.py 文件 parse() 函数内最后增加如下脚本:
## 电影名称整理 nameNew = '' for namei in name: namei = namei.replace("\n", "").replace("\xa0", "").strip() nameNew = nameNew + namei # print(nameNew) # ## 电影导演及演员整理 movieMakers = movieMakers[0].strip().replace("\n", "").replace("/...", "") ## 电影基本信息整理:年份、产地、类型 baseInfoList = baseInfo[0].strip().replace("\n", "").replace("\t", "").split("/") Myear = baseInfoList[0].strip() Mcountry = baseInfoList[1].strip() Mtype = baseInfoList[2].strip() # print(Myear, Mcountry, Mtype) ## 其他信息整理 star = star[0] score = score[0] comments = comments[0] if len(desc) != 0: desc = desc[0] else: desc = "" ## 整合所有信息 movieInfoList = [str(DoubanSpider.movieN), nameNew, movieMakers, Myear, Mcountry, Mtype, star, score, comments, desc] movieInfoStr = "^".join(movieInfoList) print(movieInfoStr) DoubanSpider.movieN = DoubanSpider.movieN + 1
再次启动爬虫 scrapy crawl douban,输出结果如下:
优化2
以 for 循环提取当前页面所有电影信息
import scrapy class DoubanSpider(scrapy.Spider): name = 'douban' start_urls = ['https://movie.douban.com/top250'] movieN = 1 def parse(self, response): # pass movieList = response.xpath("/html/body/div[3]/div[1]/div/div[1]/ol/li") for movieListi in movieList: ## 电影名称 name = movieListi.xpath(".//div/div[2]/div[1]/a//text()").getall() ## 电影创作人员 movieMakers = movieListi.xpath("./div/div[2]/div[2]/p[1]/text()[1]").getall() ## 电影年份、国家、类型 baseInfo = movieListi.xpath("./div/div[2]/div[2]/p[1]/text()[2]").getall() ## 电影评价 star = movieListi.xpath("./div/div[2]/div[2]/div/span[1]/@class").getall() score = movieListi.xpath("./div/div[2]/div[2]/div/span[2]/text()").getall() comments = movieListi.xpath("./div/div[2]/div[2]/div/span[4]/text()").getall() ## 电影介绍 desc = movieListi.xpath("./div/div[2]/div[2]/p[2]/span/text()").getall() # print(name) # print(movieMakers) # print(baseInfo) # print(star) # print(score) # print(comments) # print(desc) ## 电影名称整理 nameNew = '' for namei in name: namei = namei.replace("\n", "").replace("\xa0", "").strip() nameNew = nameNew + namei # print(nameNew) # ## 电影导演及演员整理 movieMakers = movieMakers[0].strip().replace("\n", "").replace("/...", "") ## 电影基本信息整理:年份、产地、类型 baseInfoList = baseInfo[0].strip().replace("\n", "").replace("\t", "").split("/") Myear = baseInfoList[0].strip() Mcountry = baseInfoList[1].strip() Mtype = baseInfoList[2].strip() # print(Myear, Mcountry, Mtype) ## 其他信息整理 star = star[0] score = score[0] comments = comments[0] if len(desc) != 0: desc = desc[0] else: desc = "" ## 整合所有信息 movieInfoList = [str(DoubanSpider.movieN), nameNew, movieMakers, Myear, Mcountry, Mtype, star, score, comments, desc] movieInfoStr = "^".join(movieInfoList) print(movieInfoStr) DoubanSpider.movieN = DoubanSpider.movieN + 1
5. 启动爬虫
方式一、终端执行
命令:
scrapy crawl duoban
方式二、脚本执行
新建脚本文件 main.py,通过脚本提交 cmd 命令启动爬虫
#!/usr/bin/python # -*- coding: utf-8 -*- ''' 需求说明: Scrapy 爬虫入口。 ''' from scrapy import cmdline if __name__ == '__main__': cmdline.execute('scrapy crawl douban'.split())
开始实验
第3节 Scrapy 进阶
1. 爬虫回调爬取下一页
基本思路:
- 获取下一页url
- 当下一页不是none时,就继续爬取
yield 的作用是边执行边返回,response.follow() 以新一页更新url 继续爬取,并回调解析函数 parse 提取爬取结果
import scrapy class DoubanSpider(scrapy.Spider): name = 'douban' start_urls = ['https://movie.douban.com/top250'] movieN = 1 def parse(self, response): # pass movieList = response.xpath("/html/body/div[3]/div[1]/div/div[1]/ol/li") for movieListi in movieList: ## 电影名称 name = movieListi.xpath(".//div/div[2]/div[1]/a//text()").getall() ## 电影创作人员 movieMakers = movieListi.xpath("./div/div[2]/div[2]/p[1]/text()[1]").getall() ## 电影年份、国家、类型 baseInfo = movieListi.xpath("./div/div[2]/div[2]/p[1]/text()[2]").getall() ## 电影评价 star = movieListi.xpath("./div/div[2]/div[2]/div/span[1]/@class").getall() score = movieListi.xpath("./div/div[2]/div[2]/div/span[2]/text()").getall() comments = movieListi.xpath("./div/div[2]/div[2]/div/span[4]/text()").getall() ## 电影介绍 desc = movieListi.xpath("./div/div[2]/div[2]/p[2]/span/text()").getall() # print(name) # print(movieMakers) # print(baseInfo) # print(star) # print(score) # print(comments) # print(desc) ## 电影名称整理 nameNew = '' for namei in name: namei = namei.replace("\n", "").replace("\xa0", "").strip() nameNew = nameNew + namei # print(nameNew) # ## 电影导演及演员整理 movieMakers = movieMakers[0].strip().replace("\n", "").replace("/...", "") ## 电影基本信息整理:年份、产地、类型 baseInfoList = baseInfo[0].strip().replace("\n", "").replace("\t", "").split("/") Myear = baseInfoList[0].strip() Mcountry = baseInfoList[1].strip() Mtype = baseInfoList[2].strip() # print(Myear, Mcountry, Mtype) ## 其他信息整理 star = star[0] score = score[0] comments = comments[0] if len(desc) != 0: desc = desc[0] else: desc = "" ## 整合所有信息 movieInfoList = [str(DoubanSpider.movieN), nameNew, movieMakers, Myear, Mcountry, Mtype, star, score, comments, desc] movieInfoStr = "^".join(movieInfoList) print(movieInfoStr) DoubanSpider.movieN = DoubanSpider.movieN + 1 nextPage = response.xpath('/html/body/div[3]/div[1]/div/div[1]/div[2]/span[3]/a/@href').get() if nextPage is not None: nextPage = DoubanSpider.start_urls[0] + nextPage yield response.follow(nextPage, callback=self.parse)
2. 爬虫结果写入文件
定义容器字段
文件items.py 新建类 DoubanItem,定义数据容器字段
# Define here the models for your scraped items # # See documentation in: # https://docs.scrapy.org/en/latest/topics/items.html import scrapy class ScrapytestItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() pass class DoubanItem(scrapy.Item): Mid = scrapy.Field() name = scrapy.Field() movieMakers = scrapy.Field() Myear = scrapy.Field() Mcountry = scrapy.Field() Mtype = scrapy.Field() star = scrapy.Field() score = scrapy.Field() comments = scrapy.Field() desc = scrapy.Field()
更新容器数据
文件 douban.py,引入新建类 DoubanItem,新建类 DoubanItem 的实例对象 doubanItem,并更新对象的值
- doubanItem 实例对象的键需与 items.py 中类 DoubanItem 的变量名一致,douban.py 增加如下代码:
from scrapytest.items import DoubanItem ## 电影信息保存至容器,用法类似Python 的字典 doubanItems = DoubanItem() doubanItems['Mid'] = str(DoubanSpider.movieN) doubanItems['name'] = nameNew doubanItems['movieMakers'] = movieMakers doubanItems['Myear'] = Myear doubanItems['Mcountry'] = Mcountry doubanItems['Mtype'] = Mtype doubanItems['star'] = star doubanItems['score'] = score doubanItems['comments'] = comments doubanItems['desc'] = desc yield doubanItems ##返回 doubanItems
写入数据
执行爬虫,数据导出到 json文件
方式一
命令:
scrapy crawl douban -O doubantop250.json
方式二
更新脚本文件 main.py,可以采用json 文件 或 json lines 文件,具体如下
#!/usr/bin/python # -*- coding: utf-8 -*- ''' 需求说明: Scrapy 爬虫入口。 ''' from scrapy import cmdline if __name__ == '__main__': # ## 提交启动爬虫命令行 # cmdline.execute('scrapy crawl douban'.split()) # ## 提交启动爬虫命令行:爬取信息导出到json 文件,参数:-O 覆盖任何现有文件,-o 将新内容附加到任何现有文件 # cmdline.execute('scrapy crawl douban -O doubantop250.json'.split()) ## 提交启动爬虫命令行:新内容附加到json 文件会是文件内容变为无效json。附加到文件时可以考虑json lines cmdline.execute('scrapy crawl douban -O doubantop250.jl'.split())
3. 爬虫结果写入数据库
安装第三方库
安装第三方库 pymysql
pip install pymysql -i https://pypi.tuna.tsinghua.edu.cn/simple/
创建数据库
- 创建数据库 scrapytest,注意编码,因为爬取数据以utf-8保存
创建数据表
- 创建数据表 quotes
更新管道文件
- 更新pipelines.py,增加插入类
import pymysql class ScrapytestPipeline: def process_item(self, item, spider): return item class DoubanPipeline(ScrapytestPipeline): def __init__(self): ## 创建数据库链接 hostid = "192.168.245.101" # 数据库主机地,本机MySQL的为localhost username = "root" # 数据库用户名 password = "123456" # 数据库密码 self.mydb = pymysql.connect(host=str(hostid), user=str(username), passwd=str(password)) ## 获得操作游标 self.mycursor = self.mydb.cursor() print("连接数据库成功") def process_item(self, item, spider): # sql语句 insert_sql = """ insert into scrapytest.doubantop250 (Mid, `name`, movieMakers, Myear, Mcountry, Mtype, star, score, comments, `desc`) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) """ # 执行插入数据到数据库操作 self.mycursor.execute(insert_sql, (item['Mid'], item['name'], item['movieMakers'], item['Myear'], item['Mcountry'], item['Mtype'], item['star'], item['score'], item['comments'], item['desc'])) # 提交,不进行提交无法保存到数据库 self.mydb.commit() def close_spider(self, spider): # 关闭游标和连接 self.cursor.close() self.connect.close()
更新配置文件
- 更新配置文件 setting.py,ITEM_PIPELINES(项目管道),300为优先级,越低越爬取的优先度越高
ITEM_PIPELINES = { 'scrapytest.pipelines.DoubanPipeline': 300, }
开始实验
第4节 附录