因为课题需求,需要从网站上挖掘一些有用的数据以构建模型。网站虽然提供整合的数据库,但每条数据只给了网址链接,自己从中找具体的数据内容。

网页文本挖掘其实并不是什么新鲜事儿,记得高中的时候就尝试通过“寻找规律法”从网页中获取自己想要的数据,当初还用的是Visual Basic。到了大学,学了一段时间的PHP,并没有用此自己做网页,倒是从中学习了如何用PHP采集门户网站的信息。 这些方法基本都是同样的原理,根据某信息前后数据比如<div class=”content”><span>我需要的</span>…</div>,那么采集这个数据只需要确定网站只有一个class=content的层,这样寻找到<div class=”content”><span>的位置,以及该位置后第一个出现</span>的位置,那么当中这些信息就是我所需要的。
这种方法虽然简单实用,但是碰到稍微复杂的情况,比如网站不止一个content,亦或是<span>里面万一夹杂着参数可变的attributes,那么采集起来就不那么容易了。

自从开始用了Python,就对其爱不释手,除了因为其语言简洁,还有就是有太多优秀的“小程序”(或者说Package)由Python开发,方便引用并开发。用Python写爬虫脚本,相信已经有不少优秀的程序,我找到了俩,ScrapyPyspider。感觉前者倾向于一次性的爬虫,轻量级,可扩展性强。后者更倾向是采集服务器。根据不同的需求可以选择不同的工具,两者的挖掘规律虽有不同,但也大同小异。由于我仅仅是需要一次性得到已知网址的所有需要的信息,所以我选择了Scrapy,下面是我的使用笔记。

安装:

正如其首页写的那么简单:pip install scrapy
当然,这也意味着我们需要先安装pip,方法不多做介绍。windows下不需要安装,python安装包里自带,记得添加 %PYTHONPATH%/scripts 到环境变量里,否则输入pip系统不识别。

解析框架:

这里引用官方首页里面的代码

import scrapy

class BlogSpider(scrapy.Spider):
name = ‘blogspider’
start_urls = [‘http://blog.scrapinghub.com']

def parse(self, response):
for url in response.css(‘ul li a::attr(“href”)’).re(r’.*/\d\d\d\d/\d\d/$’):
yield scrapy.Request(response.urljoin(url), self.parse_titles)

def parse_titles(self, response):
for post_title in response.css(‘div.entries > ul > li a::text’).extract():
yield {‘title’: post_title}


​可见所谓规则是一个继承的类,最重要的类成员就是start_urls,它是个list,决定了最初要挖掘哪些网页。python脚本一个好处是容易“扩展”,在这里你可以直接使用with open来打开一个文件获取url赋值给start_urls。

最重要的方法就是parse,它相当于是一个回调函数,当系统读取start_urls里面的网址的内容后,则会调用这个函数进行解析。如果要解析的网页里包含你想要进一步解析的链接(比如采集一个博客,先要采集它的文章列表,然后进一步解析文章列表里的每个链接对应的网站),那么可以用yield scrapy.Request(url, anther callback function),用另一个回调函数来解析那个具体的文章页面。当然,一般链接用的都是相对路径,所以scrapy也给了response.urljoin函数来解决这个问题。
有经验的人不难发现,采集离不开这几个关键点,初始网址用以解析“文章列表”,然后通过链接每个文章,进一步解析“文章内容”。具体在这两个回调函数里写规则

解析规则:

不难发现,每个回调函数的参数是response,可以理解为整个网页的源代码,当然已经封装成了一个对象,我们可以直接调用css,xpath两种方法进行解析。css与xpath都可以解析,规则差不多,看个人爱好。首页给的是css例子,我这里介绍下xpath。
如果我要挖取<..很多级…><div class=”content”><span>my word</span></div><…..>
那么只要response.xpath(‘//div[@class=”content”][1]/span/text()’).extract()就能得到一个list,内容是[‘my word’]
解释下这句话的意思
// 越级寻找,在本例中,无论div前面被多少个标签包裹,只要是符合条件的div都找到
div[…] 寻找符合条件的div标签,“
[@class=”content] @表示标签属性,即这个div的class内容为”content”,双引号是需要的,(通常html里都用双引号,所以我们在写规则的时候用单引号表示字符串,原因的话,熟悉Python肯定得知道吧~~
[1] 上面虽没,但很重要,表示第一个符合条件的标签,条件可以叠加,比如[@class=”content”][1],不过这可能会产生误区,详情点这里看官方文档介绍
/ 下一级寻找,也就是说span必须在前面找到的div的下一级,否则无效
text() 标签里面的文本

xpath函数得到的是selector列表,我们可以对这个进一步用xpath,而如果要获得最终我们要的数据,则最终要用extract()函数。
由于我的例子中符合条件的只有一个my word,所以最终生成单值列表。而如果符合条件的不知一个比如:
<div class=”content”><span>1</span><span>2</span></div><div class=”content”><span>3</span><span>4</span></div>
那么1、2、3、4都会在list里。如果有一个条件不符合,比如class不对,或者span不在div的下一级而是下两级,那么就不会有。

多层解析

很多时候可能不能通过一个规则就能得到,需要多层解析。先解释下我自己定义的多层解析
如果我只是div = response.xpath(‘//div[@class=”content”]),那么会得到一个selector列表,(有兴趣的话可以输出一下看看selector对象到底是什么),我们可以for each in div: (显然对应那两个div层),我们可以
div.xpath(./span/text()).extract()

注意“.“的意思是从这一级开始,所以“./”就是div的下一级的意思。如果我们要的span又被好多东西包裹,比如<div class=”content”><table><tr><td><span>my word</后面省略>那我们可以用
<.//span/text()).extract()获取,当然这样做的副作用是,所有该(div)层下的span都能被获取。

通过这样多步寻找,我们可以获取一些信息作为判断,比如碰到
<tr><td>name</td><td>zealseeker</td></tr><tr><td><age</td><td>24</td></tr>
这样的表格,按我的方式,我会将tr作为中间体,然后判断’./td[1]/text()’是否为name。

对于Scrapy规则就差不多介绍到这里,相信这里面的规则足够我们使用了,如果要更详细更复杂的方法,建议还是好好看官方文档。

输出:

其实Scrapy对其挖到的东西是可以模型化的,具体可以参考官方文档里的“Item”,而对Item也有其自带的比较完善的输出体系,参考Item Exporters。而对于我们一般的使用,为了不找麻烦,可以直接在解析方法里添加输出方式(正如在类定义的时候添加输入方式一样那么暴力)。比如在首页案例中,我们直接把yield {‘title’:post_title}改成with open(‘result.txt’,’a’) as f;f.write(post_title);f.write(‘\n’)就可以了。

当挖掘的东西足够简单,我们没有必要完全使用它的所有方法。而如果需要比较复杂的挖掘体系,那么还是多花点时间在Scrapy上的研究吧,毕竟它已经替我们考虑了很多,比如输出格式可以有csv, xml等,无需我们烦恼。

最后,使用命令 scrapy runspider xxx.py 即可进行挖掘,等着result.txt里出东西把~