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,结构如下:

  1. huawei_scrapy/ 
  2.    scrapy.cfg 
  3.    huawei_scrapy/ 
  4.        __init__.py 
  5.        items.py 
  6.        pipelines.py 
  7.        settings.py 
  8.        spiders/ 
  9.            __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()
View Code

这里我们需要获取页面上的标题,链接,描述,所以定义一个对应的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
View Code

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
View Code

制定输出不同的文件

增加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
View Code

 

其中的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/

 

posted @ 2016-03-29 14:42  shhnwangjian  阅读(2554)  评论(1)    收藏  举报