首发于JackTalk
Scrapy爬虫简介

Scrapy爬虫简介

因为工作的关系,需要从OMIM抓取一些数据下来,其实网站是有提供API和数据下载的,只是对于盈利性单位,需要获得许可。无奈之下,只能祭出爬虫来解决了。


什么是爬虫

爬虫又称蜘蛛程序,是一种网页抓取漫游器,是模拟浏览器和网页做交互的工具。英文称为「Bot」,比如微软的爬虫,称「bingbot」,每隔一定的时间,对网页进行采样,发现新的网页或更新过的网页,经过计算之后决定是否添加到索引。爬虫的规模,可大可小。大的像搜索引擎的爬虫,会涉及大量机器和分布式的机构,小的比如我今天用的,只是一个电脑,代替手动输入400个网页和一堆复制粘贴操作。

爬虫的应用,除了上面说的搜索引擎,还有数据分析,分析的数据除了是自己产生的,还有可能是从网上收集的。有的网站会提供数据获取接口,比如github、KEGG、NCBI和Ensembl,还有stackoverflow。一般收集这些网站的数据,相对就简单得多了,直接根据自己的需求,按它的接口规则拼出一个http网址就可以了。从其他没有API的网站收集数据,就要依靠爬虫了。想搜狗就依靠自己做搜索的技术基础,做了知乎和微信搜索,这两个我经常用,要写一个题目之前,先去搜索一番,如果想讲的内容别号已经写过,那就发其他内容。

还有一种情况,严格意义上用到的不只是爬虫,但也是通过模拟浏览器行为达到目的。比如公司内部有一个出具报告的网站系统,但是做的时候没给你留API接口,可每次想到要对着几十个位点,一个个点选输入检测结果,就想爆粗口,为啥不是分析完,直接从服务器上传结果呐?这时候用到的就是,模拟用户登入,选择跳转到指定样本的界面,分析POST请求参数,然后按要求拼好参数,提交。这个用到的是自动化测试的东西了,以后再写。


Scrapy使用

关于Scrapy的安装和入门指南,官网文档的内容已经很好,这里以爬取OMIM网站为例,介绍简单的使用和可能遇到的问题。

  • 确定抓取内容

    omim.org/entry/601365为例,我们关心的是基因型-表型的对照表,只有一个table,这样省去了从多个表选出一个的麻烦。再找几个看看,omim.org/entry/131244 额,还有多行表格的(除表头外),这是一个不幸的消息。

  • 定义规则

    既然决定了,就要继续做,毕竟工作不就是解决一个个问题的过程吗?通常从网页提取表格内容就不是一个省力气的活。还好我们有xpath。它是Scrapy支持的一套筛选器,用来告诉Scrapy引擎,选中网页的某个(或一组)元素,想了解更多可以参考w3school文档 。举个例子,使用’//table/tbody/tr’可以选中OMIM网页表格的第一个tr(table record,就是表格表头以外的行)

  • 构建数据输出格式

    提取到想要的内容以后,需要构建一个输出格式,比如是字典还是元组。Scrapy支持以json格式保存结果到文本,很方便。

  • 构建输入url

    Scrapy官网文档给的例子,还有网上很多中文教程的例子,输入网址都是通过给出一个start_urls的列表。我要用的url有400个,而且需要先从文本中取出来OMIM ID,分别加上网址前缀,再传给Request函数。这里用的方法和他们有些不同,通过重写scrapy类的构造函数(__init__)实现。

def __init__(self, filename=None):
    URL_BASE = 'https://www.omim.org/entry/'
if filename:
with open(filename, 'r') as f:
            omim_ids = [re.findall(r'\((\d+)\)', line )  for line in f if line.startswith('chr') ]
            self.start_urls = [ '{0}{1}'.format(URL_BASE, int(omim[0]) ) for omim in omim_ids if omim]

反爬虫应对

针对部分网站的爬虫,像Scrapy网站给出的例子那样,照猫画虎就可以达到目的了,但OMIM不行。直接开爬的话,服务器会丢一个403「拒绝访问」的错误码过来。OMIM robots这个文件规定了,网站只许Googlebot和bingbot爬它指定目录下的数据。

关于反爬的应对,官网也有资料可以参考:avoiding getting banned,参照这里和OMIM robots,修改程序设置,再测试就顺利拿到数据。用到的方法总结起来就是:

  • 换user agent,比如使用浏览器的
  • 禁止cookie
  • 限制两次抓取动作之间的间隔,这里设为4 sec,OMIM robot限制是2 sec

以下是程序代码:


#!/usr/bin/env python
#coding=utf-8

import re
import scrapy

class omimSpider(scrapy.Spider):
    name = 'omim'
    custom_settings = {
'USER_AGENT':'bingbot', #冒充bingbot的UA名和'BOT_NAME'
'BOT_NAME':'bingbot',
'COOKIES_ENABLED':False,
'DOWNLOAD_DELAY':4
    }


def __init__(self, filename=None):
        URL_BASE = 'https://www.omim.org/entry/'
if filename:
with open(filename, 'r') as f:
                omim_ids = [re.findall(r'\((\d+)\)', line )  for line in f if line.startswith('chr') ]
                self.start_urls = [ '{0}{1}'.format(URL_BASE, int(omim[0]) ) for omim in omim_ids if omim]
#                 self.start_urls = ['https://www.omim.org/entry/131244']
def parse(self, response):
        gene_pheno = {}
        gene_pheno['url'] = response.url
        gene_pheno['omim_num'] = re.findall(r'(\d+)',response.url)[0]
#extract from Description
if 'Description' in response.text:
            gene_pheno['description'] = ','.join(response.xpath('//div[@id="descriptionFold"]/span/p/text()').extract() )
#extract from table
if response.xpath('//table'):
            tr = response.xpath('//table/tbody/tr') #maybe one or more trs

            gene_pheno['location'] = tr[0].xpath('td')[0].xpath('span/a/text()').extract_first().strip() #first td of first tr in table
            phenotype = tr[0].xpath('td')[1].xpath('span/text()').extract_first().strip()
            mim = tr[0].xpath('td')[2].xpath('span/a/text()').extract_first()
            inherit = ','.join(tr[0].xpath('td')[3].xpath('span/abbr/text()').extract() )
            pheno_key = tr[0].xpath('td')[4].xpath('span/abbr/text()').extract_first()

            gene_pheno[mim] = [phenotype, inherit, pheno_key]

if len(tr) > 1:
for tb_record in tr[1:]: #less num of column than the tr above
                    phenotype = tb_record.xpath('td')[0].xpath('span/text()').extract_first().strip()
                    min = tb_record.xpath('td')[1].xpath('span/a/text()').extract_first()
                    inherit = ','.join(tb_record.xpath('td')[2].xpath('span/abbr/text()').extract() )
                    pheno_key = tb_record.xpath('td')[3].xpath('span/abbr/text()').extract_first()
                    gene_pheno[mim] = [phenotype, inherit, pheno_key]
return gene_pheno

使用方法


#scrapy 版本 1.1.0
scrapy runspider omim.py -o all_omim.json -a filename=/path/to/file.txt 

参考:
Scrapy官网文档
w3school xpath文档
omim.py源码

weixin.qq.com/r/tETI0BD (二维码自动识别)

编辑于 2017-01-03 23:21

文章被以下专栏收录