scrapy 基础爬虫指南


其实自己的爬虫经历比较少,到目前为止也就3次左右的样子。
不过这三次却也获得了不少入门经验,值得总结一下,如果以后遇到更有挑战性的项目,再来丰富。
(以下爬虫内容均基于 python scrapy 库)

参考资料:

爬虫,个人简单理解,就是反向解析网页的操作。在创建网页的时候,会用到对应的语法,包括 HTML、CSS、JS(JavaScript)。而在爬取网页信息的时候,就是解析这些语法,获得目标信息。所以,爬虫过程,相当一部分时间都花在了确定解析式

在平时需求不高的时候,你甚至可以通过 wgetcurl 等命令下载网页,使用 grep 等方法利用正则表达式获取想要的信息。
当需要更高级、复杂的信息的时候,专门的爬虫工具就非常方便了。

part1 使用 selector 解析 HTML 文本

selectorscrapy 内含的提取 HTML 页面信息的函数功能,可通过 CSS 或 xpath 两种方式的表达式对 HTML 进行解析。
(练习的时候不妨下载一个准备爬取的网页到本地)。
这里我就以生信人经常访问的 NCBI 的 GEO 数据库,查询一个测序项目为例,获取其中的 Title、Organism、Source 等信息。
GEO example

准备工作

from scrapy.selector import Selector

# 把截图中的页面下载到本地
htmlfile = 'GPL10558.html' 
htmlfo = open(htmlfile, 'r', encoding='utf-8')
htmlhandle = htmlfo.read()
pagedata = Selector(text = htmlhandle)

寻找解析式

这也是最重要的部分。
前面也谈到过,有 CSS 和 xpath 两种,你可以根据自身情况自行选择。小主我最开始接触 xpath,所以习惯用 xpath 了。scrapy官方文档关于 xpath 的指南。

这里我介绍两种快速获取解析式的方法:

网页中按 F12,进入开发者模式

最开始元素都是折叠起来的
F12界面

  1. 点击打开折叠。鼠标放在右侧开发栏,左侧对应位置会有蓝色框标记,如上图,可以根据这一点确定目标元素位置
  2. 找到目标元素后,右键→ 复制 → 复制xpath;这里以 title 元素为例。建议复制完整xpathcopy xpath界面

Google 浏览器使用 xpath 插件

自行查找安装和详细教程。

  1. Ctrl+Shift+x 快速调出 xpath 界面
  2. 按住 shift,将鼠标放在 目标元素上面,即可获取。xpath界面

根据解析式获取目标元素

根据第一种方法获得了解析式:

title_xpath = '/html/body/table/tbody/tr/td/table[6]/tbody/tr[3]/td[2]/table/tbody/tr/td/table/tbody/tr/td/table[2]/tbody/tr/td/table[1]/tbody/tr[3]/td[2]'
pagedata.xpath(title_xpath) # 如果你用 css 就是 pagedata.css(title_xpath) 了
# output: []

既然这么简单就获得了解析式,为什么这一步要花相当多的时间?
从上面的例子可以看到,其实根据直接 copy 过来的解析式,输出结果为空,也就是解析式不正确。这个问题的原因很多,有的是因为 tbody 的存在导致的问题,也有其他问题,具体问题,具体分析吧。
所以,就要自己手动尝试了:从最开始,一点一点增加信息,直至获得正确的解析式。

pagedata.xpath('/html/body/table')
# output: [<Selector xpath='/html/body/table' data='<table width="740" border="0" cellspa...'>]
pagedata.xpath('/html/body/table/tbody')
# output: [].这里就是 tbody 的问题
pagedata.xpath('/html/body/table/tr/td/table[6]/tr[3]/td[2]/table/tr/td/table/tr/td/table[2]/tr/td/table[1]/tr[3]/td[2]')
# output:[<Selector xpath='/html/body/table/tr/td/table[6]/tr[3]/td[2]/table/tr/td/table/tr/td/table[2]/tr/td/table[1]/tr[3]/td[2]' data='<td style="text-align: justify">Illum...'>]

到这已经获得了目标元素。为了获取元素内部的信息,需要加上一些提示:

  • .get() 获取第一个匹配内容,字符串格式
  • .getall() 获取所有匹配内容,形成列表
  • .extract_first() 同 get()
  • .extract()
  • .extend()
  • CSS 还有其他的属性,详见scrapy selector

为了获得 title 对应的文本,就可以这样:

title_xpath_func = title_xpath.replace('tbody/','')
pagedata.xpath(title_xpath_func+'/text()').get()
# output: 'Illumina HumanHT-12 V4.0 expression beadchip'

至此,我们就找到了完全正确的解析式。
用同样的方法,你可以处理其他目标信息,等所有目标信息的解析式都修正好之后,可以把它们放在一个字典中

xpath_dict = {
    'Title': title_xpath,
    'Organism': organism_xpath,
    ...
}

然后将整个的解析过程,包装成一个函数,每个页面的输出结果形成一个字典,循环处理每个页面,最后合并到一起写入文件。

part2 scrapy 爬取网页

在 part1 的基础之下,就可以进一步,爬取在线页面了。
scrapy 主体就要来了:

创建项目

进入你想放置项目代码的文件夹,然后

scrapy startproject tutorial

项目目录:

tutorial/
    scrapy.cfg            # deploy configuration file
    tutorial/             # project's Python module, you'll import your code from here
        __init__.py
        items.py          # project items definition file
        middlewares.py    # project middlewares file
        pipelines.py      # project pipelines file
        settings.py       # project settings file
        spiders/          # a directory where you'll later put your spiders
            __init__.py

添加爬虫 spider

spiders 子文件夹下创建爬虫文件, 例:

import scrapy

class QuotesSpider(scrapy.Spider):
    name = "quotes"

    def start_requests(self):
        urls = [
            'http://quotes.toscrape.com/page/1/',
            'http://quotes.toscrape.com/page/2/',
        ]
        for url in urls:
            yield scrapy.Request(url=url, callback=self.parse)

    def parse(self, response):
        page = response.url.split("/")[-2]
        filename = f'quotes-{page}.html'
        with open(filename, 'wb') as f:
            f.write(response.body)
        self.log(f'Saved file {filename}')

这里看不懂不要急,可跳过。

运行 spider

回到项目顶级目录下,终端运行:

scrapy crawl quotes

你不妨根据官网示例走一遍,以获得成就感,给自己做下去的信心~

spider 详解

现在我们再来好好看看 spider 做了什么(scraping cycle):
scrapy spider

  1. 首先初始化请求,抓取第一个 url,然后指定一个回调函数(cellback function)从请求中下载响应response(其实就是 part1 中的pagedata,实现了在线网页内容的读取)。根据官网示例,第一个请求就是通过 start_requests() 方法获得的,parse 作为 Request 的回调函数。
  2. 在回调函数中,使用 Selector(或者其他的库如,BeautifulSoup, lxml等)解析响应,获得目标信息 item object。
  3. 把 spider 返回的项目加到数据库中(在 items.py 中定义 item)。

spider 中的类型

  • name 指爬虫的名字,在一个项目中,具有唯一性。
  • allowed_domains 一个可选的字符串列表,其中包含允许此爬行器爬行的域。假设你的目标网址是 https://www.example.com/1.html ,然后在列表中加上 example.com。
  • start_urls 放置网址的列表。这里可以多说两句,如果,你已经有了所有需要爬取的网页网址,全部放在 start_urls 列表中,循环遍历每个页面即可;需要翻页的话,仅把第一页的网址放入即可,通过修改网页中的页数来进行爬取多个页面(如果网址中显示制定了页数的话,如果没有,需要在页面内寻找相应元素,这里不再拓展)。
  • 其他设置请参见官网。

其实官网的例子是实现了每个网页单独存到一个 HTML 文件中的。我觉得这还没有体现 scrapy 最常用的一个保存每个页面目标元素的方法,就是利用 yield 和 items.py,输出结果。
下面我自己写的一个 spider 用来爬取 NOcode 数据库中的信息:

import scrapy
import re
from lncrna.items import LncrnaItem

class collectlncRNAfromNONcode(scrapy.Spider):
    name = 'collect-RNAcent'
    allowed_domain = ['noncode.org']
    count = 1
    base_url = "http://www.noncode.org/browse.php?org=D.Melanogaster&class=transcript&PageSize=500&page="
    start_urls = [
        "http://www.noncode.org/browse.php?org=D.Melanogaster&class=transcript&PageSize=500&page=1",
    ]
    def parse(self, response):
        pages_content = response.xpath("//table[@class='mPlist_infoB']/tr[3]/td/form/table[1]/tbody/tr/td[1]/font/text()").extract()[0]
        # 获取页数
        page_number = re.findall('Page:\d+',pages_content)[0].split(':')[1]
        # 目标信息
        for tb in response.xpath("//table[@class='mPlist_infoB']/tr/td/form/table[2]/tr")[1:]:
            item = LncrnaItem()
            item['name'] = tb.xpath('td/a/text()').extract()[0]
            content = tb.xpath('td/text()').extract()
            item['chrom'] = content[0]
            item['start'] = content[1]
            item['end'] = content[2]
            # 把 item 中的信息追加到 items 中
            yield item

        if self.count + 1 <= int(page_number):
            self.count += 1
            next_url = self.base_url+str(self.count)
            # 转到下一页
            yield scrapy.Request(next_url, callback=self.parse)
        else:
            return

关于 yield 的详解,可参考python中yield的用法详解

items.py 中的设置

import scrapy

class LncrnaItem(scrapy.Item):
    # define the fields for your item here like:
    name = scrapy.Field()
    chrom = scrapy.Field()
    start = scrapy.Field()
    end = scrapy.Field()

scrapy shell 测试解析式

之前测试解析式都是使用下载到本地的 HTML,scrapy 也要这样吗?当然不需要啦。
在顶级目录下,运行如下命令:

scrapy shell "https://scrapy.org" --nolog

然后,shell 获取 URL (使用 Scrapy 下载程序)并打印可用对象列表有用的快捷方式(这些行都以[s]前缀开头):

[s] Available Scrapy objects:
[s]   scrapy     scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s]   crawler    <scrapy.crawler.Crawler object at 0x7f07395dd690>
[s]   item       {}
[s]   request    <GET https://scrapy.org>
[s]   response   <200 https://scrapy.org/>
[s]   settings   <scrapy.settings.Settings object at 0x7f07395dd710>
[s]   spider     <DefaultSpider 'default' at 0x7f0735891690>
[s] Useful shortcuts:
[s]   fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)
[s]   fetch(req)                  Fetch a scrapy.Request and update local objects
[s]   shelp()           Shell help (print this help)
[s]   view(response)    View response in a browser

就会进入到 ipython 环境中,利用 part1 中的方法,对 response 进行提取文本的测试,直到找到正确的解析式就好了。

>>> response.xpath('//title/text()').get()
'Scrapy | A Fast and Powerful Scraping and Web Crawling Framework'

更多玩法请参考scrapy shell


文章作者: 梁绍波
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 梁绍波 !
评论
  目录