最近在各个平台上学习python爬虫技术,林林总总接触到了三大类型的爬虫技术——【1】利用urllib3实现,【2】使用Requests库实现,【3】使用Scrapy框架实现。
虽然是按照以上的顺序进行学习的,但是在学习scrapy的过程中问题比较多,所以先从它开始。Python爬虫学习(一)之简单实现、Python爬虫学习(二)之Requests库将先添加至@TO-DO list里。
对于Scrapy的学习采取了阅读文档然后直接上手一个小项目的方式。这个项目的任务是爬取豆瓣电影 Top 250,把250部电影的名字、封面图以及评价数爬下来并储存至文件中。
Scrapy is a fast high-level web crawling and web scraping framework, used to crawl websites and extract structured data from their pages. It can be used for a wide range of purposes, from data mining to monitoring and automated testing.(https://docs.scrapy.org/)
Scrapy 是高级的、高速的网络爬虫框架,它被应用于爬取网站以及从中提取结构化数据。Scrapy框架应用广泛,适用于从数据挖掘到监控以及自动化测试等场景。
从Scrapy的架构图可以看出,这是一个系统化、高集成的爬虫框架。它免去了我们在许多爬取的具体细节上的编码过程,而让我们专注于我们最终的目的。个人认为Scrapy是我接触的这三种爬虫中集成度、自定义程度最高的实现方式。
安装Scrapy
因为电脑上安装了PyPI,因此采取了最简便的安装方式:
在cmd中输入pip install scrapy
,等待流程结束即可。
在cmd中输入pip show scrapy
后如果显示版本号等信息就表示安装成功了。
新建项目
cmd进到所需目录,然后输入scrapy startproject [-projectname]
,其中projectname为项目名称。系统会返回如下代码:
随后按照提示输入cd doubanmovie
和
scrapy genspider [spidername] [url]
(其中spidername是爬虫的名字,url是起始页码),一个基本的爬虫项目就建立成功了。
将文件夹导入PyCharm中,项目结构如下图所示:
经过上面的步骤,一个系统缺省的爬虫创建完毕。但是缺省的爬虫只是一个空壳,它不会完成任何实际的任务。我们还需要对它进行编码,让它能够满足我们的需求。
第一步:确定需求
我们创建爬虫的目的是获取网页上的信息,在本项目中我们是要获取250部电影的序号、(中文)名称、图片地址以及评论数,最后按照顺序将他们输出到一个文本文件中。
从第一步我们确定了需要①序号、②电影名、③图片地址、④评价数这四个数据。因此,我们先打开items.py文件,定义这四个属性。代码如下:
class DoubanmovieItem(scrapy.Item):
# define the fields for your item here:
em = scrapy.Field() #序号
title = scrapy.Field() #标题
img = scrapy.Field() #图片路径
comment = scrapy.Field() #评论数
第三步:编写爬虫逻辑(douban.py)
在编写爬虫逻辑之前,我们必须先要明白网页上的信息是如何存放的,以及在python中如何解析和定位我们所下载的网页。网页解析有几种方法,Beautiful Soup、Xpath等。BeautifulSoup我放在之前的文章中,这里主要采用Xpath方法来解析并定位网页信息。
了解XPath
XPath 是一门在 XML 文档中查找信息的语言。因为HTML也是一种XML语言,因此Xpath当然可以应用于html页面。
基本语法:
1. 选取节点
XPath 使用路径表达式在 XML 文档中选取节点。节点是通过沿着路径或者 step 来选取的。 下面列出了最有用的路径表达式:
表达式 | 描述 |
---|---|
nodename | 选取此节点的所有子节点 |
/ | 从根节点选取 |
// | 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置 |
. | 选取当前节点 |
… | 选取当前节点的父节点 |
@ | 选取属性 |
更高级的XPath语法可以去参考网上内容。
2.定位资源
在了解过XPath后,我们就可以在网页上精确地定位各类资源的位置了。
先打开豆瓣电影Top250网站, 页面空白处右键,选择“检查”。定位到每部电影的列表项处,应该是下面界面:
我们可以按照XPath语法,确定各项资源的路径。
如果xpath路径不太会写或者想节省时间,我们可以在浏览器的“检查”界面中,右键需要选取的资源,在“复制”子菜单中选择“复制XPath路径”。这样我们就可以方便地找到资源对应的XPath路径了。
确定item中元素的XPath路径
这一步主要任务是赋予每个在前面item.py中定义的变量对应在HTML中的路径。我们打开douban.py文件,可以看见系统已经自动生成了类似于下面的代码框架:
# -*- coding: utf-8 -*-
import scrapy
from lxml import etree
from doubanmovie.items import DoubanmovieItem
class DoubanSpider(scrapy.Spider):
name = 'douban'
allowed_domains = ['movie.douban.com/top250']
start_urls = ['http://movie.douban.com/top250']
def parse(self, response):
pass
自动生成的代码中:
‘parse’函数的response参数是downloader模块传来的,里面包含了下载器下载下来的内容。我们就是在这里对这些内容进行操作。
name
变量是表示这个爬虫的名称;
allowed_domains
定义了爬虫爬取的范围,不属于这个域名下的页面不会被爬取;
start_urls
定义了爬虫起始页面,爬虫从这个页面开始爬取。
在这里我们将allowed_domains
设置为 ‘movie.douban.com/top250’,即从Top250的第一页开始。
设置好后,我们用一个变量接收response的文本数据。在这个过程中,response.text
返回的是网页内容的字符串,因此,我们利用lxml包中的etree方法将response.text
返回的字符串转换为带节点的html格式。
这是etree.HTML方法的说明:
HTML(text, parser=None, base_url=None)
Parses an HTML document from a string constant. Returns the root node (or the result returned by a parser target). This function can be used to embed “HTML literals” in Python code.
具体的代码如下:
def parse(self, response):
# print(response.text.encode('unco'))
html_text = etree.HTML(response.text)
li_list = html_text.xpath("//ol[@class='grid_view']/li")
for li in li_list:
item = DoubanmovieItem()
# em = title = img = comment
item['em'] = li.xpath(".//em/text()")[0]
item['title'] = li.xpath(".//span[@class='title']/text()")[0]
item['img'] = li.xpath(".//img/@src")[0]
item['comment'] = li.xpath(".//div[@class='star']/span/text()")[-1]
# yield返回当前电影的数据
# print("--item--", item)
yield item
【注】这里必须使用yield而不能使用return,因为框架必须获得每次爬取的item,这样最后才能拼接成一个完整的结果。
第四步:设置自动翻页
首先我们必须知道下一页的链接,它有可能是利用现有的链接进行拼接,因此我们要针对具体情况来分析。
我们在“后页”按钮上右键检查,可以看到后页的链接是本页链接加上一个"?start=XX&filter="
。这样我们就可以得到下一页的链接:next_page = html_text.xpath("//span[@class='next']/a/@href")[0]
在知道下一页链接后,我们要手动发送请求,让爬虫去解析下一页的数据:
if self.next_page:
# 手动发送请求,让爬虫去解析下一页的数据
yield scrapy.Request('http://movie.douban.com/top250' + self.next_page,callback=self.parse,dont_filter=True)
【注意】scrapy.Request
的参数中,dont_filter=True
表示取消地址过滤。在我运行过程中,如果不加这个参数,爬虫在爬取下一页时,将会报
DEBUG: Filtered offsite request to ‘movie.douban.com’:
起初每次爬虫爬取完第一页后,就报这个错误,并且爬虫也停止爬取。因为提示不是ERROR,所以我并没有特别注意它,后来在网上一搜,发现这个是因为过滤器把下一页给过滤了,所以才导致爬完第一页就暂停了。
解决办法也很简单,就是在调用scrapy.Request
时,加上dont_filter=True
参数。
最后是douban.py的完整代码:
#douban.py
# -*- coding: utf-8 -*-
import scrapy
from lxml import etree
from doubanmovie.items import DoubanmovieItem
class DoubanSpider(scrapy.Spider):
name = 'douban'
allowed_domains = ['movie.douban.com/top250']
start_urls = ['http://movie.douban.com/top250']
next_page = ''
def parse(self, response):
# print(response.text.encode('unco'))
html_text = etree.HTML(response.text)
li_list = html_text.xpath("//ol[@class='grid_view']/li")
for li in li_list:
item = DoubanmovieItem()
# em = title = img = comment
item['em'] = li.xpath(".//em/text()")[0]
item['title'] = li.xpath(".//span[@class='title']/text()")[0]
item['img'] = li.xpath(".//img/@src")[0]
item['comment'] = li.xpath(".//div[@class='star']/span/text()")[-1]
# yield返回当前电影的数据
yield item
# 获取后页超链接的值 (xpath返回的是list)
self.next_page = html_text.xpath("//span[@class='next']/a/@href")[0]
print("--next_page--:" + self.next_page)
if self.next_page:
# 手动发送请求,让爬虫去解析下一页的数据
yield scrapy.Request('http://movie.douban.com/top250' + self.next_page, callback=self.parse,dont_filter=True)
第五步:设置输出方式
我们把爬虫逻辑编写好后,爬虫就可以按照我们的要求工作。但是我们通常需要把爬虫爬取的结果进行处理或保存,就像炼油厂把石油炼制后,要将石油装桶一样。这个步骤我们需要在pipelines.py
中完成。
在官方文档中是这样描述pipelines.py
用途的:
Typical uses of item pipelines are:
- cleansing HTML data
- validating scraped data (checking that the items contain certain fields)
- checking for duplicates (and dropping them)
- storing the scraped item in a database
item pipelines的常规用途为:
- 清理HTML数据
- 验证说爬取到的数据(确认items包含指定域)
- 验证数据重复性(并移除重复数据)
- 将爬取到的项目存入数据库
我们打开项目中的pipelines.py
文件,可以发现在类下面有process_item(self, item, spider)
方法,这个方法将会被每一个pipeline组建调用。其中item参数为(本次)爬取到的项,spider参数为当前爬虫。
在本项目中,我准备将每部电影的序号、标题、封面链接以及评论数依次写入一个文本文档,并将文本文档的名称设置为“豆瓣Top250”加上当前日期。
我们首先要引入time包,之后再获取当前时间并与“豆瓣Top250”拼接起来,最后再将item中的每个项目依次写入文件中。注意,write方法默认是不会换行的,数据将会紧接着之前的数据写入,因此我们要在每一部电影最好写入一个换行符“\n”。
这部分完整代码如下:
#pipelines.py
import time
class DoubanmoviePipeline(object):
def process_item(self, item, spider):
now = time.strftime('%Y-%m-%d', time.localtime())
fileName = '豆瓣250' + now + '.txt'
with open(fileName, 'a', encoding='utf-8') as fp:
fp.write(item['em']+': ')
fp.write(item['title']+' ')
fp.write(item['img']+' ')
fp.write(item['comment'])
fp.write('\n')
return item
第六步:相关设置
至此,我们已经几乎完成了一个爬虫的定义,我们最后要做的就是对爬虫进行最后的设置。设置主要有三部分:1,设置user-agent,让爬虫能够伪装成正常的访问请求;2,打开pipelines通道;3,选择是否遵循robots协议。
1. 设置user-agent
有些网站为了减少网站负担或其它原因,会设置反爬措施。如果监测到本次访问是非人类正常访问,可能会禁止本次访问或者设置真人验证。避免反爬的最基本的措施是设置一个普通的user-agent,这样让网站认为这次访问是由一个普通浏览器发出的。
打开“settings.py”,在里面找到DEFAULT_REQUEST_HEADERS
字段,取消其注释状态,把它改为:
DEFAULT_REQUEST_HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/82.0.4080.0 ' \
'Safari/537.36 Edg/82.0.453.2',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-US;q=0.7',
'Cookie': 'bid="HgstQtgjnX8"; ap_v=0,6.0',
}
里面’User-Agent’是必须的,其余可选。
2. 打开pipeline通道
我们设置了pipelines.py后,还必须在settings.py中将ITEM_PIPELINES
进行如下修改:
ITEM_PIPELINES = {
'myproject.pipelines.PricePipeline': 300
}
这样我们在pipelines.py中设置的操作才能实现。这个设置项的具体意义可以参考文档
3. 选择是否遵守robots协议
robots协议是网站在其根目录下放置的一个文本文件,其中规定了爬虫可以爬取的范围。一般来说,如果爬虫爬取速度与人类访问的速度没有特别大的差异的情况下,可以把这种爬虫视作人类行为。此时,我们可以选择不遵守robots规则。但如果爬虫是有商业目的或可能对网站造成过大的访问量,此时我们应该遵循网址的robots声明。如果不遵循robots声明可能会有法律风险。
我们在settings.py文件中的ROBOTSTXT_OBEY
字段可以设置是否遵守网站robots协议,此项默认是False,即不遵守。
本文首先简单介绍了Scrapy及其框架结构,随后安装配置Scrapy,最后针对豆瓣Top250网站进行实践。包含了user_agent伪装、pipeline输出、Xpath介绍以及自动翻页等特点,按照这个流程可以对一些基本的网站进行爬取。之后会继续对IP伪装、自动登陆、验证码绕过等特性进行学习。
写这篇文章的目的是为了记录学习Scrapy框架的过程中的感想以及一些要点,目前来说还是处于入门阶段,对Scrapy的使用也是十分基础的。欢迎各位大佬提出意见与建议,也欢迎像我一样的初学者共同学习进步。