python scrapy 获取华为应用市场APP评论数据
scrapy入门
四步:
1. 创建一个新的Scrapy Project
2. 定义你需要从网页中提取的元素Item
3. 实现一个Spider类,通过接口完成爬取URL和提取Item的功能
4. 实现一个Item PipeLine类,完成Item的存储功能
新建工程
首先,为我们的爬虫新建一个工程,首先进入一个目录(任意一个我们用来保存代码的目录),执行:
scrapy startproject huawei_scrapy
最后的huawei_scrapy就是项目名称。这个命令会在当前目录下创建一个新目录tx_scrapy,结构如下:
- huawei_scrapy/
- scrapy.cfg
- huawei_scrapy/
- __init__.py
- items.py
- pipelines.py
- settings.py
- spiders/
- __init__.py
scrapy.cfg: 项目配置文件
items.py: 需要提取的数据结构定义文件
pipelines.py: 管道定义,用来对items里面提取的数据做进一步处理,如保存等
settings.py: 爬虫配置文件
spiders: 放置spider的目录
定义Item
在Scrapy中,items是用来加载抓取内容的容器,有点像Python中的Dict,也就是字典,但是提供了一些额外的保护减少错误。
一般来说,item可以用scrapy.item.Item类来创建,并且用scrapy.item.Field对象来定义属性(可以理解成类似于ORM的映射关系)。
接下来,我们开始来构建item模型(model)。
首先,我们想要的内容有:
名称(name)
链接(url)
描述(description)
修改tx_scrapy目录下的items.py文件,在原本的class后面添加我们自己的class。我们可以将其命名为TxItem:
1 import scrapy 2 3 class HuaweiScrapyItem(scrapy.Item): 4 f_content = scrapy.Field() 5 f_time = scrapy.Field() 6 link = scrapy.Field()
这里我们需要获取页面上的标题,链接,描述,所以定义一个对应的items结构,不像Django里面models的定义有那么多种类的Field,这里只有一种就叫Field(),再复杂就是Field可以接受一个default值
实现Spider
spider只是一个继承字scrapy.spider.BaseSpider的Python类,有三个必需的定义的成员
name: 名字,这个spider的标识
start_urls:
一个url列表,spider从这些网页开始抓取
parse():
一个方法,当start_urls里面的网页抓取下来之后需要调用这个方法解析网页内容,同时需要返回下一个需要抓取的网页,或者返回items列表
所以在spiders目录下新建一个spider,名称为huawei.py
1 #/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 4 from scrapy.spider import Spider 5 from scrapy.selector import Selector 6 from ..items import HuaweiScrapyItem 7 from scrapy.selector import HtmlXPathSelector 8 from scrapy.http import Request 9 from ..pipelines import HuaweiScrapyPipeline 10 import re 11 import time 12 13 class HaiweiSpider(Spider): 14 pipeline = set([HuaweiScrapyPipeline,]) 15 name = "huawei" 16 allowed_domains = ["huawei.com"] 17 start_urls = [ 18 "http://appstore.huawei.com/app/C27936" 19 ] 20 21 def parse(self, response): 22 sel = HtmlXPathSelector(response) 23 link = response.url 24 numstr = sel.select('//*[@id="commentForm"]/h4/span/text()').extract()[0] 25 num = re.search('\d+', numstr).group() # 获取评论数 26 print num 27 se = int(time.time()) 28 if int(num) % 5 > 0: 29 page_num = int(num) / 5 + 1 30 else: 31 page_num = int(num) / 5 32 33 34 for page in range(1, page_num + 1): # 根据页数访问 35 url = "http://appstore.huawei.com/comment/commentAction.action?appId=C27936&appName=%E4%B8%9C%E6%96%B9%E8%B4%A2%E5%AF%8C%E7%BD%91&rating=0&_page=" + str(page) + "&r=" + str(se) 36 if int(num) % 5 == 0: # 判断是否每页为5列 37 yield Request(url, meta={'link': link, 'numlist': 5}, callback=self.parse2) 38 else: 39 if page == int(num) / 5 + 1: # 判断最后一页的列数 40 num_list = int(num) % 5 41 yield Request(url, meta={'link': link, 'numlist': num_list}, callback=self.parse2) 42 else: 43 yield Request(url, meta={'link': link, 'numlist': 5}, callback=self.parse2) 44 45 def parse2(self, response): 46 hxs = Selector(response) 47 link = response.meta['link'] 48 num_list = response.meta['numlist'] 49 items = [] 50 51 for num in range(1, num_list + 1): 52 item = HuaweiScrapyItem() 53 f_content = '/html/body/div[' + str(num) + ']/p[@class="content"]/text()' 54 f_time = '/html/body/div[' + str(num) + ']/p[@class="sub"]/span[@class="frt"]/text()' 55 item['link'] = link 56 item['f_time'] = hxs.select(f_time).extract()[0].strip().encode('utf-8') 57 item['f_content'] = hxs.select(f_content).extract()[0].strip().encode('utf-8') 58 items.append(item) 59 return items
allow_domains是搜索的域名范围,也就是爬虫的约束区域,规定爬虫只爬取这个域名下的网页。
从parse函数可以看出,将链接的最后两个地址取出作为文件名进行存储。
进入huawei_scrapy目录
在运行Scrapy项目时,不是:scrapy crawl 文件夹名字
而是先找到,形如xxxx.py的文件,其中有类似于:
的代码,其中的yourRealNameSipder,才是爬虫的名字,然后运行
如:scrapy crawl huawei
最后一句INFO: Closing spider (finished)表明爬虫已经成功运行并且自行关闭了。
首先,Scrapy为爬虫的 start_urls属性中的每个URL创建了一个 scrapy.http.Request 对象 ,并将爬虫的parse 方法指定为回调函数。
然后,这些 Request被调度并执行,之后通过parse()方法返回scrapy.http.Response对象,并反馈给爬虫。
提取Item
爬取整个网页完毕,接下来的就是的取过程了。
光存储一整个网页还是不够用的。
在基础的爬虫里,这一步可以用正则表达式来抓。在Scrapy里,使用一种叫做 XPath selectors的机制,它基于 XPath表达式。
提取数据到Items里面,主要用到XPath提取网页数据:
为了方便使用XPaths,Scrapy提供XPathSelector 类,有两种可以选择,HtmlXPathSelector(HTML数据解析)和XmlXPathSelector(XML数据解析)。
必须通过一个 Response 对象对他们进行实例化操作。
你会发现Selector对象展示了文档的节点结构。因此,第一个实例化的selector必与根节点或者是整个目录有关 。
在Scrapy里面,Selectors 有四种基础的方法(点击查看API文档):
xpath():返回一系列的selectors,每一个select表示一个xpath参数表达式选择的节点
css():返回一系列的selectors,每一个select表示一个css参数表达式选择的节点
extract():返回一个unicode字符串,为选中的数据
re():返回一串一个unicode字符串,为使用正则表达式抓取出来的内容
在huawei_scrapy目录下运行
scrapy shell http://guba.eastmoney.com/
结果如下图
在Shell载入后,你将获得response回应,存储在本地变量 response中。
所以如果你输入response.body,你将会看到response的body部分,也就是抓取到的页面内容:
或者输入response.headers 来查看它的 header部分:
现在就像是一大堆沙子握在手里,里面藏着我们想要的金子,所以下一步,就是用筛子摇两下,把杂质出去,选出关键的内容。
现在的Shell为我们准备好的selector对象,sel,可以根据返回的数据类型自动选择最佳的解析方案(XML or HTML)。
比如,我们要抓取网页的标题,也就是<title>这个标签:
sel.xpath('//title')
这样就能把这个标签取出来了,用extract()和text()还可以进一步做处理。
备注:简单的罗列一下有用的xpath路径表达式:
表达式 描述
nodename 选取此节点的所有子节点。
/ 从根节点选取。
// 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
. 选取当前节点。
.. 选取当前节点的父节点。
@ 选取属性。
sel.xpath('//title').extract()
sel.xpath('//title.text()')
sel.xpath('//title.text()').extract()
获取网站的超链接
sel.xpath('//ul/li/a/@href').extract()
我们用shell做了这么久的实战,最后我们可以把前面学习到的内容应用到tx_spider这个爬虫中。
parse()方法
parse可以返回Request列表,或者items列表,如果返回的是Request,则这个Request会放到下一次需要抓取的队列,如果返回items,则对应的items才能传到pipelines处理(或者直接保存,如果使用默认FEED exporter)。那么如果由parse()方法返回下一个链接,那么items怎么返回保存? Request对象接受一个参数callback指定这个Request返回的网页内容的解析函数(实际上start_urls对应的callback默认是parse方法),所以可以指定parse返回Request,然后指定另一个parse_item方法返回items:
def parse(self, response):
# doSomething
return [Request(url, callback=self.parse_item)]
def parse_item(self, response):
# item['key'] = value
return [item]
关于解析函数的返回值,除了返回列表,其实还可以使用生成器,是等价的:
def parse(self, response):
# doSomething
yield Request(url, callback=self.parse_item)
def parse_item(self, response):
yield item
如何在解析函数之间传递值?
一种常见的情况:在parse中给item某些字段提取了值,但是另外一些值需要在parse_item中提取,这时候需要将parse中的item传到parse_item方法中处理,显然无法直接给parse_item设置而外参数。 Request对象接受一个meta参数,一个字典对象,同时Response对象有一个meta属性可以取到相应request传过来的meta。所以解决上述问题可以这样做:
def parse(self, response):
# item = ItemClass()
yield Request(url, meta={'item': item}, callback=self.parse_item)
def parse_item(self, response):
item = response.meta['item']
item['field'] = value
yield item
保存抓取的数据
scrapy crawl tx -o items.json -t json
-o 后面是导出文件名,-t 后面是导出类型。
然后来看一下导出的结果,用文本编辑器打开json文件即可。
Pipelines 处理数据
写入pipelines.py文件
1 from .checkpipe import check_spider_pipeline 2 from scrapy.exceptions import DropItem 3 from openpyxl import Workbook 4 import time 5 6 class HuaweiScrapyPipeline(object): 7 8 def __init__(self): 9 self.wb = Workbook() 10 self.ws = self.wb.active 11 self.ws.append(['URL', '评论时间', '评论内容']) 12 13 @check_spider_pipeline 14 def process_item(self, item, spider): 15 line = [item['link'], item['f_time'], item['f_content']] # 把数据中每一项整理出来 16 self.ws.append(line) # 将数据以行的形式添加到xlsx中 17 perfix = time.strftime('%Y%m%d') 18 file_name = './huawei_' + perfix + '.xlsx' 19 self.wb.save(file_name) # 保存xlsx文件 20 return item 21 22 class DuplicatesPipeline(object): 23 24 def __init__(self): 25 self.ids_seen = set() 26 27 def process_item(self, item, spider): 28 if item['f_tiime'] in self.ids_seen: 29 raise DropItem("Duplicate item found: %s" % item) 30 else: 31 self.ids_seen.add(item['f_tiime']) 32 return item
制定输出不同的文件
增加checkpipe.py文件
1 #/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 4 from scrapy.exceptions import DropItem 5 import functools 6 ''' 7 当有多个pipeline时,判断spider如何执行指定的管道 8 ''' 9 def check_spider_pipeline(process_item_method): 10 @functools.wraps(process_item_method) 11 def wrapper(self, item, spider): 12 # message template for debugging 13 msg = '%%s %s pipeline step' % (self.__class__.__name__,) 14 if self.__class__ in spider.pipeline: # 判断要执行的spider中是否包含所需的pipeline 如果有则执行否则抛出DropItem信息 15 spider.logger.debug(msg % 'executing') 16 return process_item_method(self, item, spider) 17 # otherwise, just return the untouched item (skip this step in 18 # the pipeline) 19 else: 20 spider.logger.debug(msg % 'skipping') 21 raise DropItem("Missing pipeline property") 22 return wrapper
其中的process_item方法是必须调用的用来处理item,并且返回值必须为Item类的对象,或者是抛出DropItem异常。并且上述方法将得到的item实现解码,以便正常显示中文,最终保存到json文件中。
注意:在编写完pipeline后,为了能够启动它,必须将其加入到ITEM_PIPLINES配置中,即在settings.py中加入下面一句:
ITEM_PIPELINES = {
'huawei_scrapy.pipelines.HuaweiScrapyPipeline': 300,
}
scrapy 让指定的spider执行指定的pipeline:http://www.cnblogs.com/fly-kaka/p/5216791.html
中文学习网址:http://scrapy-chs.readthedocs.org/zh_CN/latest/

浙公网安备 33010602011771号