用Scrapy写爬虫

最近懒,没怎么更新博客,赶紧在周末抽出时间写写。

前一段时间想翻翻freebuf网站的内容(黑客与画家^_^),一页一页翻太麻烦,本着黑客精神,就想着写个爬虫把网站内容题目爬下来,对感兴趣的题目可以点进去看。

先声明,原来从来没写过爬虫,于是为了偷懒,找到了Python的爬虫框架Scrapy,官方文档看这里

有框架就是快,突击学习了两天就把爬虫写出来了。

Scrapy安装

自己搜索吧,这里不提。 我用的是Ubuntu系统,刚学会了一个小技巧。安装过程中报错缺少头文件,可试着用一下命令:

sudo apt-get install apt-file 
apt-file update 
apt-file search /***  #这里代表提示缺少的文件及路径

通过上面的命令找到相关的包、软件后安装即可,注意会搜索出来很多,一般为后几个字母为lib的包。 ###创建项目 选择合适的目录,创建freebuf项目:

scrapy startproject freebuftools

该命令将创建如下目录文件:

freebuftools/
    scrapy.cfg
    freebuftools/
        __init__.py
        items.py
        pipelines.py
        settings.py
        spiders/
            __init__.py
            ...

这些文件分别是:

  • scrapy.cfg: 项目的配置文件
  • freebuftools/: 该项目的python模块。之后您将在此加入代码。
  • freebuftools/items.py: 项目中的item文件.
  • freebuftools/pipelines.py: 项目中的pipelines文件.
  • freebuftools/settings.py: 项目的设置文件.
  • freebuftools/spiders/: 放置spider代码的目录.

定义Item

Item 是保存爬取到的数据的容器;其使用方法和python字典类似, 并且提供了额外保护机制来避免拼写错误导致的未定义字段错误。

由于我只想获得freebuf里文章的题目和链接,于是只定义了两个相应字段:

import scrapy
class FreebuftoolsItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title=scrapy.Field()#用于保存题目
    link =scrapy.Field()#用于保存链接

分析网站结构

使用浏览器的审查元素功能查看freebuf网站分类浏览下的html代码,可以发现文章目录一条一条的都被包含在<div class="news_inner news-list"></div>标签里面

具体结构如下图:

文章题目结构

通过目录结构可以提取出文章题目的xpath:

//div[@class='news_inner news-list']/div[@class='news-info']/dl/dt/a/text()

文章链接的xpath:

//div[@class='news_inner news-list']/div[@class='news-info']/dl/dt/a/@href

光有文章题目和内容还不够,还要知道怎样进入下一页,查看html代码可知下一页的链接格式为

<div class="news-more" id="pagination">
    <a href="http://www.freebuf.com/xxx/page/xxx">查看更多</a>
</div>

提取出下一页链接xpath://div[@id='pagination']/a/@href 具体xpath的教程可以查看scrapy文档,或者看这里xpath教程

有一个小技巧,浏览器中看html代码时可以右键选择复制xpath,不过复制的路径比较死板。

编写spider

首先在目录freebuftools\freebuftools\spiders\下新建文件freebuftools_Spider.py。 代码如下:

#encoding=utf-8
import scrapy
from freebuftools.items import FreebuftoolsItem
class FreeBufToolsSpider(scrapy.Spider):
    name = "freeBufTools"
    allowed_domains =["freebuf.com"]#域名
    start_urls =["http://www.freebuf.com/geek"]#初始页面,可更改为相应分类首页
    def parse(self,response):
        file=open("freebufgeek.html",'a')#写入爬取内容的文件,注意文件为追加模式

        for sel in response.xpath("//div[@class='news_inner news-list']/div[@class='news-info']/dl/dt/a"):#xpath路径父节点
            tools=FreebuftoolsItem()#新建保存item
            tools['title']=sel.xpath("text()").extract()[0].encode('utf-8')#提取文章题目并转换格式保存
            tools['link']=sel.xpath("@href").extract()[0].encode('utf-8')#提取文章链接并转换格式保存
            html='''<p><a href="'''+tools['link']+'''" target="_black">'''+tools['title']+"</a></p>"#拼接写入文件的html代码
            file.writelines(html)#将爬取内容写入文件
            yield tools#返回爬取内容
        url=response.xpath("//div[@id='pagination']/a/@href").extract()[0]#获取下一页的链接
        yield scrapy.Request(url,callback=self.parse)#回调本函数继续爬取下一页

上面代码写的比较清楚,内容也很简单,不懂的可以查看官方文档,另外有点需要注意,保存爬取内容的文件打开方式为追加,如果不这样只能保存一条爬取的数据。

改变初始页面可以爬取不同分类下的文章题目。

还有Python中yield的用法,简单来说就是yield可在函数中返回内容而仍使函数能继续执行。具体Google百度。

执行爬虫

打开最外层freebuftools文件夹,运行命令scrapy crawl freeBufTools即可。运行完后可找到自己定义的爬取内容的文件。

我这里有个问题就是记得创建项目时字母用的小写,但执行时发现小写不对,大家可以在执行前先运行一下scrapy list查看项目名称。 ###遇到的问题 主要是文件的问题,一开始我是想直接通过管道导出json格式文件的,但是发现输出的文件内容是汉字对应的utf-8编码,如图:

json输出形式

最后虽然解决了输出汉字的问题但想着json文件还要写个脚本读取,就直接在spider中往文件里写内容了,代码就是上面贴的代码。解决输出内容的方法可以看这里,声明:只是用的这里的方法,自己根据具体内容做相应改动。主要pipelines.py文件和settings.py文件