说到爬虫,咱们可能会想到利用互联网快速地获取咱们须要的网络资源,例如图片、文字或者视频。这是真的,若是咱们掌握了爬虫技术,咱们就能够在短期爬取网络上的东西。可是,咱们能爬取的网上资源都是对方网站上直接摆出的内容,意味着咱们能够手动的复制粘贴来获取,而爬虫只是代替了咱们的人工,并加快这一过程而已。在进行编写网络爬虫以前,咱们必需要确保咱们所想获取的网络资源有迹可循,知道咱们所要的网络资源在哪一个网站或者请求接口,而后咱们还要确保咱们所须要的资源是可获取的,由于有时候若是对方不但愿本身的资源被随意爬取,他们的网站可能存在一些验证机制,来拦截网络爬虫。css
进行网络爬虫的具体过程就是:批量获取网站网页的html内容(或者经过网站的资源接口获取数据)->编写数据处理代码处理数据转化为咱们所须要的资源。若是有网站的数据接口,爬虫就会简单不少,咱们通常使用requests就能够直接获取数据。然而大多数状况是,咱们须要的数据(或数据接口)是在网页的html中,在正式获取数据以前咱们还须要先进行html的页面内容解析及提取。html页面获取,内容解析,数据提取还有最后的数据存储,有时还须要考虑防爬虫机制,这看起来须要进行很多的工做,而python有个第三方库,能够帮助咱们简化这些工做,而且提升爬虫效率。它就是scrapy。html
如Scrapy官网所说,Scrapy是一个用于爬取网站数据,提取结构性数据的应用框架。今天咱们就用scrapy来开始尝试咱们的第一次爬虫。前端
咱们新建一个文件夹用于开展咱们的爬虫项目,名为douban_scrapy。此次咱们要爬取的目标资源是豆瓣的高分电影,豆瓣有人作高分电影汇总,可是有点很差的是咱们没有办法按时间或者分数高低对,也不能按按电影类型分类查看,因此咱们能够将上面的数据爬取下来,自行整理。python
在开始安装scrapy前,咱们在项目路径下新建个python环境(这是个好习惯,能够防止安装环境的冲突):shell
python -m venv ./douban_scrapy_env
source ./douban_scrapy_env/bin/activate
而后执行pip install scrapy
安装scrapy库数据库
如今咱们能够开始使用scrapy,执行scrapy startproject douban
就会在当前路径下自动新建项目文件夹douban,该文件夹下包含如下文件:json
douban/
scrapy.cfg
douban/
__init__.py
items.py
middlewares.py
pipelines.py
settings.py
spiders/
__init__.py
复制代码
其中:浏览器
使用scrapy编写爬虫,要先定义items,而后再在spiders文件夹下写爬虫脚本。可是如今,咱们还一头雾水,由于咱们还没去了解咱们要爬取的网页。下面咱们试试用scrapy的终端调试功能来熟悉咱们要爬取的网页。markdown
咱们能够用scrapy shell [url]
的命令打开抓取对应网页的终端,同时爬取下来的抓取结果response,咱们能够基于它来调试咱们提取数据的规则。如今咱们试试用scrapy shell https://www.douban.com/doulist/30299/
命令来爬取一下豆瓣高分电影列表第一页:网络
能够看到咱们爬取的结果返回403,这意味着咱们这样爬取他们的网页被他们的防爬虫机制拦截了。对此,咱们试试在命令后加-s USER_AGENT='Mozilla/5.0'
:
咱们成功获取了正常的爬取结果,这说明对方是经过请求头信息判断出咱们是爬虫脚本,经过改变USER_AGENT来假装成咱们是经过浏览器访问的。若是咱们不想每次执行命令都加上USER_AGENT参数,咱们能够在项目文件夹下的setting文件设定USER_AGENT='Mozilla/5.0'
。
如今咱们能够开始调试爬取规则。咱们知道,response是咱们爬取后的结果对象,当咱们基于此对象创造选择器后,就能够经过这个选择器用scrapy的命令提取咱们须要的内容,创造选择器有两种方式:
from scrapy.selector import Selector
response.selector
在scrapy中,咱们能够对选择器使用css或xpath方法来提取咱们须要的数据。对于前端有所了解的话会知道css是前端定义样式的语言,而在这里,选择器对象中设定了css方法,参数是基于样式提取数据的规则语句。xpath也是选择器中定义的用于提取数据的函数,只是xpath的规则语法与css不一样。本文主要使用xpath方法,下面介绍xpath语法的基本规则(用abc等单一字符代替html元素):
response.selector.xpath("a")
, 提取指定环境下没有父元素的a元素response.selector.xpath("a/b")
, 提取a元素下的第一级元素bresponse.selector.xpath("//a")
, 提取页面中全部a元素无论元素位于什么位置response.selector.xpath("a//b")
, 提取a元素下全部b元素,无论b元素在a元素下第几级response.selector.xpath("a/@b")
, 提取a元素下的b属性response.selector.xpath("a/b[1]")
, 提取a元素下第一级b元素中的第一个response.selector.xpath("//a[@b]")
提取全部含有b属性的a元素response.selector.xpath("//a[@b='c']")
, 提取全部b属性值为c的a元素response.selector.xpath("//a[contains("b", "c")]")
, 提取全部拥有b属性,且b属性值中包含c字符的a元素 如今咱们认识了xpath的部分语法规则,让咱们试试提取出页面列表中全部电影的名字。咱们先执行view(response)
把网页在浏览器打开,而后用浏览器的开发者工具模式查看包含电影名的html内容。
根据咱们的观察,咱们发现:电影列表的每一项都存放在类名为doulist-item
的div里,该div下有个类名为mod的div存放内容,类名为mod的div下有个类名为bd doulist-subject
的div存放主体内容,而后该div下类名为title
的div里的a标签就存放着咱们须要的电影名, 这页面内的电影名大都如此存放(咱们得出如此结论是由于页面内电影列表里的每一项样式都是同样的),因此我能够用以下规则提取出页面中每一个电影的名字:
response.selector.xpath("//div[@class='doulist-item']/div[@class='mod']/div[@class='bd doulist-subject']/div[@class='title']/a/text()").extract()
复制代码
由于咱们要的是a标签下的文本内容,因此咱们在a标签路径后接text()
代表咱们要这个元素下的文本。xpath函数返回的是由一个个selector对象组成的列表,咱们须要用extract函数,把selector对象转换为纯文本。
上图就是咱们提取出的页面中电影名,由于提取出的文本还带有一些额外的换行和退格符,因此咱们再对每一项提取的字符串使用strip函数获得最后提取结果。咱们对结果仔细观察发现提取的结果里有空白字符串,按理来讲咱们的提取路径应该是彻底一致的,为什么结果里会偶而穿插字符串,难道是页面内的电影名处有什么样式不同的地方。
咱们看到第二个电影名处比第一个多了一个播放图标,难道这就是咱们提取到空白字符串的缘由?下面咱们把第一个提取到的第二个包含电影名的selector展开看看:
咱们从图中看到,提取到的包含电影名的a标签元素,若是里面有电影图标,前面就会有一个包含换行符和空格的字符串,这就是咱们提取到的电影名去除多余字符后有空白字符串的缘由。当咱们提取到的文本有两段,这意味着前一段是空白字符串,后一段才是咱们要的电影名,那么咱们每次都取最后一段文本就好了,因此咱们能够把以前的xpath规则再作修改:
response.selector.xpath("//div[@class='doulist-item']/div[@class='mod']/div[@class='bd doulist-subject']/div[@class='title']/a/text()[last()]").extract()
复制代码
这里咱们在text()后面加上[last()]代表咱们要的只是最后的文本。此外,由于咱们使用xpath返回的是selector列表,咱们是对selector对象使用xpath函数的,对selector对象列表也能使用。这意味着咱们能够对xpath返回的结果再次使用xpath进行提取,这样可让代码结构更清晰,以上电影名提取代码等同:
films_box = response.selector.xpath("//div[@class='doulist-item']/div[@class='mod']")
films_body = films_box.xpath("div[@class='bd doulist-subject']")
films_name = films_body.xpath("div[@class='title']/a/text()[last()]").extract()
复制代码
最后咱们再总结一下得出用scrapy的xpath提取目标数据规则的流程:
咱们如今分析出了电影列表中电影名字的提取规则,这意味着咱们能够开始写爬虫脚本了。在scrapy中,咱们要写爬虫脚本钱,先要定义items,即须要爬取的目标实体。在scrapy中定义的item为一个个类,类名为item名,类中直接定义的类变量做为item的属性,例如:
import scrapy
class Film(scrapy.item):
name = scrapy.Field()
复制代码
以上是咱们定义的电影Film的item,具备电影名name这一属性值。建立的item类继承scrapy.item,在声明item属性时使用scrapy.Field,不须要指定类型。咱们建立item主要目的是使用scrapy的自动保存数据功能,例如把提取结果按咱们定义的item结构导出为excel文件或保存到数据库。
咱们知道怎么定义item以后就能够开始考虑编写spider脚本了,编写spider有如下几个要点:
import Spider from scrapy
,编写爬虫类时须要继承此类下面咱们编写豆瓣电影爬虫脚本:
import scrapy
from douban.items import Film
class DouBan(scrapy.Spider):
name = "douban"
start_urls=[""https://www.douban.com/doulist/30299/""]
def parse(self, response):
films_box = response.selector.xpath("//div[@class='doulist-item']/div[@class='mod']")
films_body = films_box.xpath("div[@class='bd doulist-subject']")
films_name = films_body.xpath("div[@class='title']/a/text()[last()]").extract()
for name in films_name:
name = name.strip()
film = Film()
film["name"] = name
yield film
复制代码
在以上爬虫脚本中咱们设置爬虫的名字为douban,要爬取的页面以及在parse函数中设定解析爬取结果并提取须要数据的方法。咱们完成爬虫脚本后就能够执行scrapy crawl douban -o 文件名
把提取到的数据保存到文件中,scrapy支持保存的文件类型有'json', 'jsonlines', 'jl', 'csv', 'xml', 'marshal', 'pickle'
里面一开始是不包含excel文件格式的,可是导出为excel文件是咱们最想要的(利用excel功能直接筛选或者排序电影结果)。
为此咱们能够安装python库scrapy-xlsx
,它的主要功能就是让咱们在scrapy项目中能够把咱们的结果导出为excel文件。在项目环境下执行pip install scrapy-xlsx
安装库,后在项目文件夹下的settings.py文件里设置:
FEED_EXPORTERS = {
'xlsx': 'scrapy_xlsx.XlsxItemExporter',
}
复制代码
如今咱们能够在项目文件夹路径下执行scrapy crawl douban -o douban_films.xlsx
把结果导出成excel文件
咱们以前是在xpath中使用last()方法只取最后一段文原本过滤空白的电影名,若是咱们遇到没法使用xpath语法来处理无用文本时,咱们是否须要在spider脚本里进行处理?其实,scrapy提供了咱们方便处理和保存提取结果的方式,名为pipeline。pipeline意思为管道,这里咱们使用pipeline就像是构建新的产品流水线环节,在这流水线上咱们会对产品进行检测处理,或者将产品如何封装保存。因此,咱们定义pipeline主要是为了如下四点:
咱们以前说过项目文件夹下有个pipelines.py文件,咱们就是在这里定义咱们的pipeline。每一个定义的pipeline为一个类,类名能够随意,可是里面必需要包含方法process_item(item, spider), 在这里咱们能够获取scrapy提取到的item并进行处理。如今,咱们试试编写DoubanPipeline,丢失电影名为空白字符串的item:
from scrapy.exceptions import DropItem
class DoubanPipeline:
def process_item(self, item, spider):
if item["name"]:
return item
else:
raise DropItem("丢弃空白电影名")
复制代码
如今咱们编写了一个Pipeline,咱们要让scrapy使用它,在settings.py里设置:
ITEM_PIPELINES = {
'douban.pipelines.DoubanPipeline': 300,
}
复制代码
settings.py里的ITEM_PIPELINES项能够定义提取数据时须要使用的pipeline,里面的key是pipeline的路径,值是0-1000范围内的数字,用于决定pipeline的启用顺序。
如今咱们能够修改一下咱们原来的爬虫脚本:
def parse(self, response):
films_box = response.selector.xpath("//div[@class='doulist-item']/div[@class='mod']")
films_body = films_box.xpath("div[@class='bd doulist-subject']")
films_name = films_body.xpath("div[@class='title']/a/text()").extract()
for name in films_name:
name = name.strip()
film = Film()
film["name"] = name
yield film
复制代码
咱们在parse函数里没有在xpath语句中使用last(), 也没有再判断和过滤空白的电影名,由于咱们已经有了一个用于过滤电影名的pipeline,如今咱们再次执行scrapy crawl douban -o douban_films.xlsx
能够看到后台输出的信息里显示,当提取到的电影名为空白字符串,就会抛弃而且输出咱们自定义的警告信息,而且最后获得的输出文件结果也是和咱们原来的一致。
如今咱们知道如何从页面内提取结果,可是咱们目标的豆瓣电影列表可不止一页。为了爬取全部电影列表数据,咱们须要知道每一页对应的连接。
咱们观察了页面下面带数字123页面跳转标签看到了他们的每个标签对应连接,发现每一个连接除了start=后面的数字不同其余都是一致的,而且每一个连接的start=后面的值以25单调递增,第一页电影列表项为25,因此咱们就知道每个连接start=后面数字应该是页数乘以25的积。
如今咱们知道了怎么生成每个要爬取网页的连接,如今咱们还要知道何时停下。咱们能够看到在跳转页面的标签里表明当前页的span标签有个属性值为data-total-page,这就是咱们所须要的。
咱们知道,当咱们设定了start_urls后,scrapy会爬取里面全部链接而且默认执行parse函数,因此咱们能够在start_urls里放置第一页的连接,而后在parse函数里提取data-total-page值,而后返回因此要爬取的scrapy.Request对象(带咱们生成的连接),scrapy,Request对象的回调函数为parse_page,咱们自定义的用于提取电影信息的函数:
class DouBan(scrapy.Spider):
name = "douban"
start_urls = ["https://www.douban.com/doulist/30299/"]
def parse(self, response):
page_total = response.selector.xpath("//span[@class='thispage']/@data-total-page").extract()
if page_total:
page_total = int(page_total[0])
for i in range(page_total):
url = "https://www.douban.com/doulist/30299/?start={}&sort=seq&playable=0&sub_type=".format(i*25)
yield scrapy.Request(url, callback=self.parse_page)
def parse_page(self, response):
films_box = response.selector.xpath("//div[@class='doulist-item']/div[@class='mod']")
films_body = films_box.xpath("div[@class='bd doulist-subject']")
films_name = films_body.xpath("div[@class='title']/a/text()").extract()
for name in films_name:
name = name.strip()
film = Film()
film["name"] = name
yield film
复制代码
此外,咱们此次爬取的数据量大,若是持续爬取可能会被对方检测出来,他们应该也不但愿本身的网站被这样无心义的密集访问。对此咱们能够减慢咱们的爬虫脚本访问对方数据库的速度,在项目文件夹中的settings.py文件中设置:
AUTOTHROTTLE_ENABLED = True
DOWNLOAD_DELAY = 3
复制代码
其中:
AUTOTHROTTLE_ENABLED设置为True后开启自动限速,能够自动调整scrapy并发请求数和下载延迟,AUTOTHROTTLE_ENABLED调整的下载延迟不会低于设置的DOWNLOAD_DELAY
DOWNLOAD_DELAY用于设置下载延迟,即下次下载距上次下载须要等待的时间,单位为秒
如今咱们知道了如何调试xpath提取数据的路径、定义item、编写spider、借助pipeline处理数据、爬取每一页。咱们基本上是了解了如何用scrapy爬虫,但咱们还有个初始的目标: 把每一个电影的信息(名字、评分、评价人数、导演、主演、类型、制片国家或地区和时间)爬取下来,好方便咱们在线下本身对这些数据进行分类排序以找到咱们本身喜欢看的电影。
在咱们开始写这些信息的提取路径以前,咱们先在原来的item类Film里定义新的信息字段:
class Film(scrapy.Item):
name = scrapy.Field()
score = scrapy.Field()
rating_users = scrapy.Field()
director = scrapy.Field()
starring = scrapy.Field()
film_type = scrapy.Field()
country_regin = scrapy.Field()
year = scrapy.Field()
复制代码
如今咱们再在shell中调试每一个信息的xpath提取路径:
films_box = response.selector.xpath("//div[@class='doulist-item']/div[@class='mod']")
films_body = films_box.xpath("div[@class='bd doulist-subject']")
# 电影评分
films_score = films_body.xpath("div[@class='rating']/span[@class='rating_nums']/text()").extract()
# 评价人数
rating_users = films_body.xpath("div[@class='rating']/span[3]/text()").extract()
number_pattern = "[0-9]+"
rating_users = [re.search(number_pattern, i).group() for i in rating_users]
# 电影主要信息
films_abstract = films_body.xpath("div[@class='abstract']")
复制代码
电影主要信息里包含了咱们须要的电影导演、主演、类型等信息,如今咱们再在原来的spider脚本进行补充:
...
import re
...
def parse_page(self, response):
films_box = response.selector.xpath("//div[@class='doulist-item']/div[@class='mod']")
films_body = films_box.xpath("div[@class='bd doulist-subject']")
films_name = films_body.xpath("div[@class='title']/a/text()[last()]").extract()
films_score = films_body.xpath("div[@class='rating']/span[@class='rating_nums']/text()").extract()
rating_users = films_body.xpath("div[@class='rating']/span[3]/text()").extract()
number_pattern = "\d+"
rating_users = [re.search(number_pattern, i).group() for i in rating_users]
films_abstract = films_body.xpath("div[@class='abstract']")
for idx, name in enumerate(films_name):
name = name.strip()
film = Film()
film["name"] = name
film["score"] = films_score[idx]
film["rating_users"] = rating_users[idx]
info_list = films_abstract[idx].xpath("text()").extract()
info_list = [i.strip() for i in info_list]
for s in info_list:
if s.startswith("导演"):
film["director"] = s[4:]
elif s.startswith("主演"):
film["starring"] = s[4:]
elif s.startswith("类型"):
film["film_type"] = s[4:]
elif s.startswith("制片国家/地区"):
film["country_regin"] = s[9:]
elif s.startswith("年份"):
film["year"] = s[4:]
yield film
复制代码
如今咱们再次在项目文件夹路径下执行scrapy crawl douban -o douban_films.xlsx
就能够获得一份包含咱们全部须要信息的excel。
以上就是咱们在结果excel文件中筛选出来的评分大于8.9,以评价人数降序排列后的电影信息,也就是你们一致认为好看的电影哦。
这篇文章内容到此就要结束了,本文至此主要讲了用scrapy的15点内容:
但愿这篇文章对你们的爬虫启蒙可以有所帮助。