Scrapy爬虫简介
因为工作的关系,需要从OMIM抓取一些数据下来,其实网站是有提供API和数据下载的,只是对于盈利性单位,需要获得许可。无奈之下,只能祭出爬虫来解决了。
什么是爬虫
爬虫又称蜘蛛程序,是一种网页抓取漫游器,是模拟浏览器和网页做交互的工具。英文称为「Bot」,比如微软的爬虫,称「bingbot」,每隔一定的时间,对网页进行采样,发现新的网页或更新过的网页,经过计算之后决定是否添加到索引。爬虫的规模,可大可小。大的像搜索引擎的爬虫,会涉及大量机器和分布式的机构,小的比如我今天用的,只是一个电脑,代替手动输入400个网页和一堆复制粘贴操作。
爬虫的应用,除了上面说的搜索引擎,还有数据分析,分析的数据除了是自己产生的,还有可能是从网上收集的。有的网站会提供数据获取接口,比如github、KEGG、NCBI和Ensembl,还有stackoverflow。一般收集这些网站的数据,相对就简单得多了,直接根据自己的需求,按它的接口规则拼出一个http网址就可以了。从其他没有API的网站收集数据,就要依靠爬虫了。想搜狗就依靠自己做搜索的技术基础,做了知乎和微信搜索,这两个我经常用,要写一个题目之前,先去搜索一番,如果想讲的内容别号已经写过,那就发其他内容。
还有一种情况,严格意义上用到的不只是爬虫,但也是通过模拟浏览器行为达到目的。比如公司内部有一个出具报告的网站系统,但是做的时候没给你留API接口,可每次想到要对着几十个位点,一个个点选输入检测结果,就想爆粗口,为啥不是分析完,直接从服务器上传结果呐?这时候用到的就是,模拟用户登入,选择跳转到指定样本的界面,分析POST请求参数,然后按要求拼好参数,提交。这个用到的是自动化测试的东西了,以后再写。
Scrapy使用
关于Scrapy的安装和入门指南,官网文档的内容已经很好,这里以爬取OMIM网站为例,介绍简单的使用和可能遇到的问题。
确定抓取内容
以https://www.omim.org/entry/601365为例,我们关心的是基因型-表型的对照表,只有一个table,这样省去了从多个表选出一个的麻烦。再找几个看看,https://www.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