Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。Scrapy最初是为了页面抓取(更确切来说, 网络抓取)所设计的,也可以应用在获取API所返回的数据(例如Amazon Associates Web Services)或者通用的网络爬虫。
1 安装
简要说明下Scrapy的安装:
下载网址:http://www.lfd.uci.edu/~gohlke/pythonlibs/
下载后缀名为whl的scrapy文件,在cmd中进入Scripts所在的位置,输入pip install scrapy文件名.whl(可参考《Python初学基础》中的7.1 模块安装),注意scrapy依赖twiste,同样使用whl格式的包进行安装。安装完这两个模块后我在进行爬虫操作的时候提示没有win32api,该文件为exe,下载地址为https://sourceforge.net/projects/pywin32/files/pywin32/Build%20220/。
在安装好模块后要注意环境变量的配置,以我自己的安装目录为例,应当将D:\Program Files (x86)\Python\Scripts以及D:\Program Files (x86)\Python\Lib\site-packages加入环境变量中,否则模块只能在安装目录下运行,在别的目录下运行时会提示不是内部或者外部命令。在cmd下输入scrapy查看是否安装成功。
上述简单介绍了scrapy的安装,在安装的过程中不要着急,如果安装出错,要注意查看错误信息,根据这些信息一个一个去解决。
2 Scrapy架构及组件介绍
使用Scrapy抓取一个网站一共需要四个步骤:
1. 创建一个Scrapy项目;
2. 定义Item容器;
3. 编写爬虫;
4. 存储内容
学习怎么使用Scrapy之前,我们需要先来了解一下Scrapy的架构以及组件之间的交互。下图展现的是Scrapy的架构,包括组件及在系统中发生的数据流(图中绿色箭头)。
下面对每个组件都做了简单介绍:
Scrapy Engine
Scrapy引擎是爬虫工作的核心,负责控制数据流在系统中所有组件中流动,并在相应动作发生时触发事件。
调度器(Scheduler)
调度器从引擎接受request并将他们入队,以便之后引擎请求他们时提供给引擎。
下载器(Downloader)
下载器负责获取页面数据并提供给引擎,而后提供给spider。
Spiders
Spider是Scrapy用户编写用于分析由下载器返回的response,并提取出item和额外跟进的URL的类。 Item Pipeline Item Pipeline负责处理被spider提取出来的item。典型的处理有清理、验证及持久化(例如存取到数据库中)。
接下来是两个中间件,它们用于提供一个简便的机制,通过插入自定义代码来扩展Scrapy的功能。
下载器中间件(Downloader middlewares)
下载器中间件是在引擎及下载器之间的特定钩子(specific hook),处理Downloader传递给引擎的response。
Spider中间件(Spider middlewares)
Spider中间件是在引擎及Spider之间的特定钩子(specific hook),处理spider的输入(就是接收来自下载器的response)和输出(就是发送items给item pipeline以及发送requests给调度器)。
3 Scrapy爬虫框架入门实例
例程参考《scrapy爬虫框架入门实例》,该例子是抓取慕课网(http://blog.csdn.net/zjiang1994/article/details/52779537)。慕课网的页面结构已经变了,所以说该案例实际上已经不能达到抓取目的。但是关于scrapy爬虫框架整体的使用方式和流程目前还是正确的,可以进行参考。根据慕课网现有的页面结构做了一些改动可以成功实现。
要抓取的内容是全部的课程名称,课程图片,课程人数,课程简介,课程URL:
右键审查元素查看
#如果response是网页资源的话,下面的代码可以帮助我们获得div divs = response.xpath('//div[@class="course-card-container"]')
所以如果div已经获得的话通过如下获得信息(详解介绍见下文):
#获取每个div中的课程路径 item['url'] = 'http://www.imooc.com' + box.xpath('.//@href').extract()[0] #获取div中的课程标题 item['title'] = box.xpath('.//h3[@class="course-card-name"]/text()').extract()[0].strip() #获取div中的标题图片地址 item['image_url'] = 'http:' + box.xpath('.//@src').extract()[0] #获取div中的学生人数 item['student'] = box.xpath('.//span/text()').extract()[1].strip() #获取div中的课程简介 item['introduction'] = box.xpath('.//p/text()').extract()[0].strip()
工作流程
Scrapy框架抓取的基本流程是这样:
当然了,还有一些中间件等等,这里是入门例子,所以不涉及。
1)创建一个Scrapy项目
在开始爬取之前,您必须创建一个新的Scrapy项目。
进入您打算存储代码的目录中,运行下列命令: scrapy startproject tutorial
该命令将会创建包含下列内容的tutorial目录:
tutorial/ scrapy.cfg tutorial/ __init__.py items.py pipelines.py settings.py spiders/ __init__.py ...
这些文件构成Scrapy爬虫框架,它们分别的作用是:
scrapy.cfg – 项目的配置文件
tutorial/ – 该项目的python模块,之后您将在此加入代码
tutorial/items.py – 项目中的item文件
tutorial/pipelines.py – 项目中的pipelines文件
tutorial/settings.py – 项目的设置文件
tutorial/spiders/ – 放置spider代码的目录
2)定义Item容器
Item是保存爬取到的数据的容器,其使用方法和python字典类似, 并且提供了额外保护机制来避免拼写错误导致的未定义字段错误。
首先根据需要获取到的数据对item进行建模。比如我们需要从慕课网中获取课程名称,课程图片,课程人数,课程简介,课程URL。对此,我们需要在item中定义相应的字段。
我们在工程目录下可以看到一个items文件,我们可以更改这个文件或者创建一个新的文件来定义我们的item。将item.py中的内容修改如下:
#引入文件 import scrapy class CourseItem(scrapy.Item): #课程标题 title = scrapy.Field() #课程url url = scrapy.Field() #课程标题图片 image_url = scrapy.Field() #课程描述 introduction = scrapy.Field() #学习人数 student = scrapy.Field() image_path = scrapy.Field()
根据如上的代码,我们创建了一个名为item的容器,用来保存、抓取的信息, title->课程标题, url->课程url, image_url->课程标题图片, introduction->课程描述, student->学习人数。在创建完item文件后我们可以通过类似于词典(dictionary-like)的API以及用于声明可用字段的简单语法。常用方法如下:
#定义一个item course = CourseItem() #赋值 course['title'] = "语文" #取值 course['title'] course.get('title') #获取全部键 course.keys() #获取全部值 course.items()
3) 创建一个爬虫
我们要编写爬虫,首先是创建一个Spider我们在tutorial/spiders/
目录下创建一个文件MySpider.py
文件包含一个MySpider类,它必须继承scrapy.Spider类。
同时它必须定义一下三个属性:
-name: 用于区别Spider。 该名字必须是唯一的,您不可以为不同的Spider设定相同的名字。
-start_urls: 包含了Spider在启动时进行爬取的url列表。 因此,第一个被获取到的页面将是其中之一。 后续的URL则从初始的URL获取到的数据中提取。
-parse() 是spider的一个方法。 被调用时,每个初始URL完成下载后生成的 Response 对象将会作为唯一的参数传递给该函数。 该方法负责解析返回的数据(response data),提取数据(生成item)以及生成需要进一步处理的URL的 Request 对象。
创建完成后MySpider.py的代码如下
#引入文件 import scrapy class MySpider(scrapy.Spider): #用于区别Spider name = "MySpider" #允许访问的域 allowed_domains = [] #爬取的地址 start_urls = [] #爬取方法 def parse(self, response): pass
为了简单清晰,我们先抓取一个页面中的信息。
首先我们编写爬取代码。我们在上文说过,爬取的部分在MySpider类的parse()方法中进行。 parse()方法负责处理response并返回处理的数据以及(/或)跟进的URL。该方法及其他的Request回调函数必须返回一个包含 Request 及(或) Item 的可迭代的对象。
在网页中提取我们所需要的数据,之前所学习的是根据正则表达式来获取,在Scrapy中是使用一种基于Xpath和CSS的表达式机制:Scrapy Selectors。
Selector是一个选择器,它有四个基本的方法:
xpath() – 传入xpath表达式,返回该表达式所对应的所有节点的selector list列表 。
css() – 传入CSS表达式,返回该表达式所对应的所有节点的selector list列表。
extract() – 序列化该节点为unicode字符串并返回list。
re() – 根据传入的正则表达式对数据进行提取,返回unicode字符串list列表。
在Shell中尝试Selector选择器
为了介绍Selector的使用方法,接下来我们将要使用内置的Scrapy shell。
你需要先进入项目的根目录,执行下列命令来启动Scrapy shell:
scrapy shell “http://www.imooc.com/course/list”
shell的输出类似:
在Shell载入后,你将获得response回应,存储在本地变量response中。
所以如果你输入response.body,你将会看到response的body部分,也就是抓取到的页面内容,或者输入response.headers 来查看它的 header部分。现在就像是一大堆沙子握在手里,里面有我们想要的金子,所以下一步我们就要用筛子把沙子去掉,淘出金子。selector选择器就是这样一个筛子,正如我们刚才讲到的,你可以使用response.selector.xpath()、response.selector.css()、response.selector.extract()和response.selector.re()这四个基本方法。
使用XPath
什么是XPath?XPath是一门在网页中查找特定信息的语言。所以用XPath来筛选数据,要比使用正则表达式容易些。
这里给出XPath表达式的例子及对应的含义:
/html/head/title – 选择HTML文档中<head>标签内的<title>元素
/html/head/title/text() – 选择上面提到的<title>元素的文字
//td – 选择所有的<td>元素
//div[@class=”mine”] – 选择所有具有class=”mine”属性的div元素
上边仅仅是几个简单的XPath例子,XPath实际上要比这远远强大的多。如果你想了解更多关于XPath的内容,推荐学习这篇文章http://www.w3school.com.cn/xpath/
值得一提的是,response.xpath()、response.css()已经被映射到response.selector.xpath()、response.selector.css(),所以直接使用response.xpath()即可。
在Python编写时,由于没有学习过Xpath,所以我先在cmd中编写试验得到正确的返回结果后再写入代码中,注意shell根据response的类型自动为我们初始化了变量sel,我们可以直接使用。
例如获取每个div中的课程路径:
此外,我们希望Spiders将爬取并筛选后的数据存放到item容器中,所以我们MySpider.py的代码应该是这样的:
import scrapy #引入容器 from tutorial.items import CourseItem class MySpider(scrapy.Spider): #设置name name = "MySpider" #设定域名 allowed_domains = ["imooc.com"] #填写爬取地址 start_urls = ["http://www.imooc.com/course/list"] #编写爬取方法 def parse(self, response): #实例一个容器保存爬取的信息 item = CourseItem() #这部分是爬取部分,使用xpath的方式选择信息,具体方法根据网页结构而定 #先获取每个课程的div for box in response.xpath('//div[@class="course-card-container"]'): #获取每个div中的课程路径 item['url'] = 'http://www.imooc.com' + box.xpath('.//@href').extract()[0] #获取div中的课程标题 item['title'] = box.xpath('.//h3[@class="course-card-name"]/text()').extract()[0].strip() #获取div中的标题图片地址 item['image_url'] = 'http:' + box.xpath('.//@src').extract()[0] #获取div中的学生人数 item['student'] = box.xpath('.//span/text()').extract()[1].strip() #获取div中的课程简介 item['introduction'] = box.xpath('.//p/text()').extract()[0].strip() #返回信息 yield item
在parse()方法中response参数返回一个下载好的网页信息,我们然后通过xpath来寻找我们需要的信息。
在scrapy框架中,可以使用多种选择器来寻找信息,这里使用的是xpath,同时我们也可以使用BeautifulSoup,lxml等扩展来选择,而且框架本身还提供了一套自己的机制来帮助用户获取信息,就是Selectors。
在执行完以上步骤之后,我们可以运行一下爬虫,看看是否出错。
在命令行下进入工程文件夹,然后运行:
scrapy crawl MySpider
如果操作正确会显示如下信息:
上面信息表示,我们已经获取了信息,接下来我们开始进行信息的储存。
最简单存储爬取的数据的方式是使用Feed exports,主要可以导出四种格式:JSON,JSON lines,CSV和XML。
我们这里将结果导出为最常用的JSON格式:
scrapy crawl dmoz -o items.json -t json
-o 后边是导出的文件名,-t 指定导出类型 成功执行命令后,根目录出现了一个叫 items.json 的文件,内容如下:
或者使用Pipeline处理数据:
当我们成功获取信息后,要进行信息的验证、储存等工作,这里以储存为例。
当Item在Spider中被收集之后,它将会被传递到Pipeline,一些组件会按照一定的顺序执行对Item的处理。
Pipeline经常进行以下一些操作:
清理HTML数据
验证爬取的数据(检查item包含某些字段)
查重(并丢弃)
将爬取结果保存到数据库中
这里只进行简单的将数据储存在json文件的操作。
改写在tutorial/
目录下文件pipelines.py的代码如下:
# -*- coding: utf-8 -*- # Define your item pipelines here # # Don't forget to add your pipeline to the ITEM_PIPELINES setting # See: http://doc.scrapy.org/en/latest/topics/item-pipeline.html #引入文件 from scrapy.exceptions import DropItem import json class MyPipeline(object): def __init__(self): #打开文件 self.file = open('data.json', 'w', encoding='utf-8') #该方法用于处理数据 def process_item(self, item, spider): #读取item中的数据 line = json.dumps(dict(item), ensure_ascii=False) + "\n" #写入文件 self.file.write(line) #返回item return item #该方法在spider被开启时被调用。 def open_spider(self, spider): pass #该方法在spider被关闭时被调用。 def close_spider(self, spider): pass
要使用Pipeline,首先要注册Pipeline
找到settings.py文件,这个文件时爬虫的配置文件
在其中添加:
ITEM_PIPELINES = { 'tutorial.pipelines.MyPipeline': 1, }
上面的代码用于注册Pipeline,其中'tutorial.pipelines.MyPipeline为你要注册的类,右侧的’1’为该Pipeline的优先级,范围1~1000,越小越先执行。
进行完以上操作,我们的一个最基本的爬取操作就完成了
这时我们再运行:
scrapy crawl MySpider
就可以在项目根目录下发现data.json文件,里面存储着爬取的课程信息。
上面的代码只进行了比较简单的爬取,并没有完成爬取慕课网全部课程的目标。
下面进行一些简单的扩展完成我们的目标。
url跟进
在上面我们介绍了如何进行简单的单页面爬取,但是我们可以发现慕课网的课程是分布在去多个页面的,所以为了完整的爬取信息课程信息,我们需要进行url跟进。
为了完成这个目标需要对MySpider.py
文件进行如下更改
import scrapy #引入容器 from tutorial.items import CourseItem class MySpider(scrapy.Spider): #设置name name = "MySpider" #设定域名 allowed_domains = ["imooc.com"] #填写爬取地址 start_urls = ["http://www.imooc.com/course/list"] #编写爬取方法 def parse(self, response): #实例一个容器保存爬取的信息 item = CourseItem() #这部分是爬取部分,使用xpath的方式选择信息,具体方法根据网页结构而定 #先获取每个课程的div for box in response.xpath('//div[@class="course-card-container"]'): #获取每个div中的课程路径 item['url'] = 'http://www.imooc.com' + box.xpath('.//@href').extract()[0] #获取div中的课程标题 item['title'] = box.xpath('.//h3[@class="course-card-name"]/text()').extract()[0].strip() #获取div中的标题图片地址 item['image_url'] = 'http:' + box.xpath('.//@src').extract()[0] #获取div中的学生人数 item['student'] = box.xpath('.//span/text()').extract()[1].strip() #获取div中的课程简介 item['introduction'] = box.xpath('.//p/text()').extract()[0].strip() #返回信息 yield item #url跟进开始 #获取下一页的url信息 url = response.xpath("//a[contains(text(),'下一页')]/@href").extract() if url : #将信息组合成下一页的url page = 'http://www.imooc.com' + url[0] #返回url yield scrapy.Request(page, callback=self.parse) #url跟进结束
修改成功后就可以自动进行url跟进了。