Python爬虫 | Scrapy详解
一.Scrapy框架简介
何为框架,就相当于一个封装了很多功能的结构体,它帮我们把主要的结构给搭建好了,我们只需往骨架里添加内容就行。scrapy框架是一个为了爬取网站数据,提取数据的框架,我们熟知爬虫总共有四大部分,请求、响应、解析、存储,scrapy框架都已经搭建好了。scrapy是基于twisted框架开发而来,twisted是一个流行的事件驱动的python网络框架,scrapy使用了一种非阻塞(又名异步)的代码实现并发的,Scrapy之所以能实现异步,得益于twisted框架。twisted有事件队列,哪一个事件有活动,就会执行!Scrapy它集成高性能异步下载,队列,分布式,解析,持久化等。
1.五大核心组件
引擎(Scrapy)
框架核心,用来处理整个系统的数据流的流动, 触发事务(判断是何种数据流,然后再调用相应的方法)。也就是负责Spider、ItemPipeline、Downloader、Scheduler中间的通讯,信号、数据传递等,所以被称为框架的核心。
调度器(Scheduler)
用来接受引擎发过来的请求,并按照一定的方式进行整理排列,放到队列中,当引擎需要时,交还给引擎。可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址。
下载器(Downloader)
负责下载引擎发送的所有Requests请求,并将其获取到的Responses交还给Scrapy Engine(引擎),由引擎交给Spider来处理。Scrapy下载器是建立在twisted这个高效的异步模型上的。
爬虫(Spiders)
用户根据自己的需求,编写程序,用于从特定的网页中提取自己需要的信息,即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面。跟进的URL提交给引擎,再次进入Scheduler(调度器)。
项目管道(Pipeline)
负责处理爬虫提取出来的item,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。
2.工作流程
Scrapy中的数据流由引擎控制,其过程如下:
(1)用户编写爬虫主程序将需要下载的页面请求requests递交给引擎,引擎将请求转发给调度器;
(2)调度实现了优先级、去重等策略,调度从队列中取出一个请求,交给引擎转发给下载器(引擎和下载器中间有中间件,作用是对请求加工如:对requests添加代理、ua、cookie,response进行过滤等);
(3)下载器下载页面,将生成的响应通过下载器中间件发送到引擎;
(4) 爬虫主程序进行解析,这个时候解析函数将产生两类数据,一种是items、一种是链接(URL),其中requests按上面步骤交给调度器;items交给数据管道(数据管道实现数据的最终处理);
官方文档
英文版:https://docs.scrapy.org/en/latest/
http://doc.scrapy.org/en/master/
中文版:https://scrapy-chs.readthedocs.io/zh_CN/latest/intro/overview.html
https://www.osgeo.cn/scrapy/topics/architecture.html
二、安装及常用命令介绍
1. 安装
Linux:pip3 install scrapy
Windows:
a. pip3 install wheel
b. 下载twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
c. shift右击进入下载目录,执行 pip3 install typed_ast-1.4.0-cp36-cp36m-win32.whl
d. pip3 install pywin32
e. pip3 install scrapy
2.scrapy基本命令行
复制代码
(1)创建一个新的项目
scrapy startproject ProjectName
(2)生成爬虫
scrapy genspider +SpiderName+website
(3)运行(crawl) # -o output
scrapy crawl +SpiderName
scrapy crawl SpiderName -o file.json
scrapy crawl SpiderName-o file.csv
(4)check检查错误
scrapy check
(5)list返回项目所有spider名称
scrapy list
(6)view 存储、打开网页
scrapy view https://www.baidu.com
(7)scrapy shell,进入终端
scrapy shell https://www.baidu.com
(8)scrapy runspider
scrapy runspider zufang_spider.py
复制代码
三、简单实例
以麦田租房信息爬取为例,网站http://bj.maitian.cn/zfall/PG1
1.创建项目
scrapy startproject houseinfo
生成项目结构:
scrapy.cfg 项目的主配置信息。(真正爬虫相关的配置信息在settings.py文件中)
items.py 设置数据存储模板,用于结构化数据,如:Django的Model
pipelines 数据持久化处理
settings.py 配置文件
spiders 爬虫目录
2.创建爬虫应用程序
cd houseinfo
scrapy genspider maitian maitian.com
然后就可以在spiders目录下看到我们的爬虫主程序
3.编写爬虫文件
步骤2执行完毕后,会在项目的spiders中生成一个应用名的py爬虫文件,文件源码如下:
复制代码
1 # -*- coding: utf-8 -*-
2 import scrapy
3
4
5 class MaitianSpider(scrapy.Spider):
6 name = 'maitian' # 应用名称
7 allowed_domains = ['maitian.com'] #一般注释掉,允许爬取的域名(如果遇到非该域名的url则爬取不到数据)
8 start_urls = ['http://maitian.com/'] #起始爬取的url列表,该列表中存在的url,都会被parse进行请求的发送
9
10 #解析函数
11 def parse(self, response):
12 pass
复制代码
我们可以在此基础上,根据需求进行编写
复制代码
1 # -*- coding: utf-8 -*-
2 import scrapy
3
4 class MaitianSpider(scrapy.Spider):
5 name = 'maitian'
6 start_urls = ['http://bj.maitian.cn/zfall/PG100']
7
8
9 #解析函数
10 def parse(self, response):
11
12 li_list = response.xpath('//div[@class="list_wrap"]/ul/li')
13 results = []
14 for li in li_list:
15 title = li.xpath('./div[2]/h1/a/text()').extract_first().strip()
16 price = li.xpath('./div[2]/div/ol/strong/span/text()').extract_first().strip()
17 square = li.xpath('./div[2]/p[1]/span[1]/text()').extract_first().replace('㎡','') # 将面积的单位去掉
18 area = li.xpath('./div[2]/p[2]/span/text()[2]').extract_first().strip().split('\xa0')[0] # 以空格分隔
19 adress = li.xpath('./div[2]/p[2]/span/text()[2]').extract_first().strip().split('\xa0')[2]
20
21 dict = {
22 "标题":title,
23 "月租金":price,
24 "面积":square,
25 "区域":area,
26 "地址":adress
27 }
28 results.append(dict)
29
30 print(title,price,square,area,adress)
31 return results
复制代码
须知:
xpath为scrapy中的解析方式
xpath函数返回的为列表,列表中存放的数据为Selector类型数据。解析到的内容被封装在Selector对象中,需要调用extract()函数将解析的内容从Selector中取出。
如果可以保证xpath返回的列表中只有一个列表元素,则可以使用extract_first(), 否则必须使用extract()
两者等同,都是将列表中的内容提取出来
title = li.xpath('./div[2]/h1/a/text()').extract_first().strip()
title = li.xpath('./div[2]/h1/a/text()')[0].extract().strip()
4. 设置修改settings.py配置文件相关配置:
复制代码
1 #伪装请求载体身份
2 USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36'
3
4 #可以忽略或者不遵守robots协议
5 ROBOTSTXT_OBEY = False
复制代码
5.执行爬虫程序:scrapy crawl maitain
爬取全站数据,也就是全部页码数据。本例中,总共100页,观察页面之间的共性,构造通用url
方式一:通过占位符,构造通用url
复制代码
1 import scrapy
2
3 class MaitianSpider(scrapy.Spider):
4 name = 'maitian'
5 start_urls = ['http://bj.maitian.cn/zfall/PG{}'.format(page) for page in range(1,4)] #注意写法
6
7
8 #解析函数
9 def parse(self, response):
10
11 li_list = response.xpath('//div[@class="list_wrap"]/ul/li')
12 results = []
13 for li in li_list:
14 title = li.xpath('./div[2]/h1/a/text()').extract_first().strip()
15 price = li.xpath('./div[2]/div/ol/strong/span/text()').extract_first().strip()
16 square = li.xpath('./div[2]/p[1]/span[1]/text()').extract_first().replace('㎡','')
17 # 也可以通过正则匹配提取出来
18 area = li.xpath('./div[2]/p[2]/span/text()[2]')..re(r'昌平|朝阳|东城|大兴|丰台|海淀|石景山|顺义|通州|西城')[0]
19 adress = li.xpath('./div[2]/p[2]/span/text()[2]').extract_first().strip().split('\xa0')[2]
20
21 dict = {
22 "标题":title,
23 "月租金":price,
24 "面积":square,
25 "区域":area,
26 "地址":adress
27 }
28 results.append(dict)
29
30 return results
复制代码
如果碰到一个表达式不能包含所有情况的项目,解决方式是先分别写表达式,最后通过列表相加,将所有url合并成一个url列表,例如
复制代码
start_urls = ['http://www.guokr.com/ask/hottest/?page={}'.format(n) for n in range(1, 8)] + [
'http://www.guokr.com/ask/highlight/?page={}'.format(m) for m in range(1, 101)]
复制代码
方式二:通过重写start_requests方法,获取所有的起始url。(不用写start_urls)
复制代码
1 import scrapy
2
3 class MaitianSpider(scrapy.Spider):
4 name = 'maitian'
5
6 def start_requests(self):
7 pages=[]
8 for page in range(90,100):
9 url='http://bj.maitian.cn/zfall/PG{}'.format(page)
10 page=scrapy.Request(url)
11 pages.append(page)
12 return pages
13
14 #解析函数
15 def parse(self, response):
16
17 li_list = response.xpath('//div[@class="list_wrap"]/ul/li')
18
19 results = []
20 for li in li_list:
21 title = li.xpath('./div[2]/h1/a/text()').extract_first().strip(),
22 price = li.xpath('./div[2]/div/ol/strong/span/text()').extract_first().strip(),
23 square = li.xpath('./div[2]/p[1]/span[1]/text()').extract_first().replace('㎡',''),
24 area = li.xpath('./div[2]/p[2]/span/text()[2]').re(r'昌平|朝阳|东城|大兴|丰台|海淀|石景山|顺义|通州|西城')[0],
25 adress = li.xpath('./div[2]/p[2]/span/text()[2]').extract_first().strip().split('\xa0')[2]
26
27 dict = {
28 "标题":title,
29 "月租金":price,
30 "面积":square,
31 "区域":area,
32 "地址":adress
33 }
34 results.append(dict)
35
36 return results
复制代码
四、数据持久化存储
基于终端指令的持久化存储
基于管道的持久化存储
只要是数据持久化存储,parse方法必须有返回值(也就是return后的内容)
1. 基于终端指令的持久化存储
执行输出指定格式进行存储:将爬取到的数据写入不同格式的文件中进行存储,windows终端不能使用txt格式
scrapy crawl 爬虫名称 -o xxx.json
scrapy crawl 爬虫名称 -o xxx.xml
scrapy crawl 爬虫名称 -o xxx.csv
以麦田为例,spider中的代码不变,将返回值写到qiubai.csv中。本地没有,就会自己创建一个。本地有就会追加
scrapy crawl maitian -o maitian.csv
就会在项目目录下看到,生成的文件
查看文件内容
2.基于管道的持久化存储
scrapy框架中已经为我们专门集成好了高效、便捷的持久化操作功能,我们直接使用即可。要想使用scrapy的持久化操作功能,我们首先来认识如下两个文件:
items.py:数据结构模板文件。定义数据属性。
pipelines.py:管道文件。接收数据(items),进行持久化操作。
持久化流程:
① 爬虫文件爬取到数据解析后,需要将数据封装到items对象中。
② 使用yield关键字将items对象提交给pipelines管道,进行持久化操作。
③ 在管道文件中的process_item方法中接收爬虫文件提交过来的item对象,然后编写持久化存储的代码,将item对象中存储的数据进行持久化存储(在管道的process_item方法中执行io操作,进行持久化存储)
④ settings.py配置文件中开启管道
2.1保存到本地的持久化存储
爬虫文件:maitian.py
复制代码
1 import scrapy
2 from houseinfo.items import HouseinfoItem # 将item导入
3
4 class MaitianSpider(scrapy.Spider):
5 name = 'maitian'
6 start_urls = ['http://bj.maitian.cn/zfall/PG100']
7
8 #解析函数
9 def parse(self, response):
10
11 li_list = response.xpath('//div[@class="list_wrap"]/ul/li')
12
13 for li in li_list:
14 item = HouseinfoItem(
15 title = li.xpath('./div[2]/h1/a/text()').extract_first().strip(),
16 price = li.xpath('./div[2]/div/ol/strong/span/text()').extract_first().strip(),
17 square = li.xpath('./div[2]/p[1]/span[1]/text()').extract_first().replace('㎡',''),
18 area = li.xpath('./div[2]/p[2]/span/text()[2]').extract_first().strip().split('\xa0')[0],
19 adress = li.xpath('./div[2]/p[2]/span/text()[2]').extract_first().strip().split('\xa0')[2]
20 )
21
22 yield item # 提交给管道,然后管道定义存储方式
复制代码
items文件:items.py
复制代码
1 import scrapy
2
3 class HouseinfoItem(scrapy.Item):
4 title = scrapy.Field() #存储标题,里面可以存储任意类型的数据
5 price = scrapy.Field()
6 square = scrapy.Field()
7 area = scrapy.Field()
8 adress = scrapy.Field()
复制代码
管道文件:pipelines.py
复制代码
1 class HouseinfoPipeline(object):
2 def __init__(self):
3 self.file = None
4
5 #开始爬虫时,执行一次
6 def open_spider(self,spider):
7 self.file = open('maitian.csv','a',encoding='utf-8') # 选用了追加模式
8 self.file.write(",".join(["标题","月租金","面积","区域","地址","\n"]))
9 print("开始爬虫")
10
11 # 因为该方法会被执行调用多次,所以文件的开启和关闭操作写在了另外两个只会各自执行一次的方法中。
12 def process_item(self, item, spider):
13 content = [item["title"], item["price"], item["square"], item["area"], item["adress"], "\n"]
14 self.file.write(",".join(content))
15 return item
16
17 # 结束爬虫时,执行一次
18 def close_spider(self,spider):
19 self.file.close()
20 print("结束爬虫")
复制代码
配置文件:settings.py
复制代码
1 #伪装请求载体身份
2 USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36'
3
4 #可以忽略或者不遵守robots协议
5 ROBOTSTXT_OBEY = False
6
7 #开启管道
8 ITEM_PIPELINES = {
9 'houseinfo.pipelines.HouseinfoPipeline': 300, #数值300表示为优先级,值越小优先级越高
10 }
复制代码
豆瓣电影TOP 250爬取-->>>数据保存到MongoDB
豆瓣电影TOP 250网址
要求:
1.爬取豆瓣top 250电影名字、演员列表、评分和简介
2.设置随机UserAgent和Proxy
3.爬取到的数据保存到MongoDB数据库
items.py
复制代码
# -*- coding: utf-8 -*-
import scrapy
class DoubanItem(scrapy.Item):
# define the fields for your item here like:
# 标题
title = scrapy.Field()
# 信息
bd = scrapy.Field()
# 评分
star = scrapy.Field()
# 简介
quote = scrapy.Field()
复制代码
doubanmovie.py
复制代码
# -*- coding: utf-8 -*-
import scrapy
from douban.items import DoubanItem
class DoubamovieSpider(scrapy.Spider):
name = "doubanmovie"
allowed_domains = ["movie.douban.com"]
offset = 0
url = "https://movie.douban.com/top250?start="
start_urls = (
url+str(offset),
)
def parse(self, response):
item = DoubanItem()
movies = response.xpath("//div[@class='info']")
for each in movies:
# 标题
item['title'] = each.xpath(".//span[@class='title'][1]/text()").extract()[0]
# 信息
item['bd'] = each.xpath(".//div[@class='bd']/p/text()").extract()[0]
# 评分
item['star'] = each.xpath(".//div[@class='star']/span[@class='rating_num']/text()").extract()[0]
# 简介
quote = each.xpath(".//p[@class='quote']/span/text()").extract()
if len(quote) != 0:
item['quote'] = quote[0]
yield item
if self.offset < 225:
self.offset += 25
yield scrapy.Request(self.url + str(self.offset), callback = self.parse)
复制代码
pipelines.py
复制代码
# -*- coding: utf-8 -*-
import pymongo
from scrapy.conf import settings
class DoubanPipeline(object):
def __init__(self):
host = settings["MONGODB_HOST"]
port = settings["MONGODB_PORT"]
dbname = settings["MONGODB_DBNAME"]
sheetname= settings["MONGODB_SHEETNAME"]
# 创建MONGODB数据库链接
client = pymongo.MongoClient(host = host, port = port)
# 指定数据库
mydb = client[dbname]
# 存放数据的数据库表名
self.sheet = mydb[sheetname]
def process_item(self, item, spider):
data = dict(item)
self.sheet.insert(data)
return item
复制代码
settings.py
复制代码
DOWNLOAD_DELAY = 2.5
COOKIES_ENABLED = False
DOWNLOADER_MIDDLEWARES = {
'douban.middlewares.RandomUserAgent': 100,
'douban.middlewares.RandomProxy': 200,
}
USER_AGENTS = [
'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0)',
'Mozi