对于一个资深程序员来说写一个爬虫其实很简单,如何写一个稳定持续运行的爬虫也不难,但是如果构建一个通用化的爬虫平台系统将为后面的工作节省很多时间。
这篇文章,我就来和你分享一下,一个通用垂直爬虫平台的构建思路。
首先介绍一下,什么是爬虫?
搜索引擎是这样定义的:
网络爬虫(又被称为网页蜘蛛,网络机器人),是一种按照一定的规则,自动地抓取网页信息的程序或者脚本。
很简单,爬虫就是指定规则自动采集数据的程序脚本,目的在于拿到想要的数据。
而爬虫主要分为两大类:
通用爬虫(搜索引擎)
垂直爬虫(特定领域)
由于第一类的开发成本较高,所以只有搜索引擎公司在做,如谷歌、百度等。
而大多数企业在做的都是第二类,成本低、数据价值高。
例如一家做电商的公司只需要电商领域有价值的数据,那开发一个只采集电商领域数据的爬虫平台,意义较大。
我要和你分享的主要是针对第二类,垂直爬虫平台的设计思路。
如何写爬虫
首先,从最简单的开始,我们先了解一下如何写一个爬虫?
简单爬虫
开发爬虫最快的语言一般是 Python,它的代码写起来非常少。我们以抓取豆瓣书籍页面为例,来写一个简单的程序。
# coding: utf8
"""简单爬虫"""
import requests
from lxml import etree
def main():
# 1. 定义页面URL和解析规则
crawl_urls = [
'http://jshk.com.cn/mb/bohao/',
'http://jshk.com.cn/mb/http.asp',
'http://jshk.com.cn/index.asp'
]
parse_rule = "//div[@id='wrapper']/h1/span/text()"
for url in crawl_urls:
# 2. 发起HTTP请求
response = requests.get(url)
# 3. 解析HTML
result = etree.HTML(response.text).xpath(parse_rule)[0]
# 4. 保存结果
print result
if __name__ == '__main__':
main()
这个爬虫比较简单,大致流程为:
定义页面URL和解析规则
发起HTTP请求
解析HTML,拿到数据
保存数据
任何爬虫,要想获取网页上的数据,都是经过这几步。
当然,这个简单爬虫效率比较低,是采用同步抓取的方式,只能抓完一个网页,再去抓下一个,有没有可以提高效率的方式呢?
异步爬虫
我们进行优化,由于爬虫的抓取请求都是阻塞在网络 IO 上,所以我们可以使用异步的方式来优化,例如多线程或协程并行抓取网页数据,这里用 Python 的协程来实现。
# coding: utf8
"""协程版本爬虫,提高抓取效率"""
from gevent import monkey
monkey.patch_all()
import requests
from lxml import etree
from gevent.pool import Pool
def main():
# 1. 定义页面URL和解析规则
crawl_urls = [
'http://jshk.com.cn/mb/bohao/',
'http://jshk.com.cn/mb/http.asp',
'http://jshk.com.cn/index.asp'
]
rule = "//div[@id='wrapper']/h1/span/text()"
# 2. 抓取
pool = Pool(size=10)
for url in crawl_urls:
pool.spawn(crawl, url, rule)
pool.join()
def crawl(url, rule):
# 3. 发起HTTP请求
response = requests.get(url)
# 4. 解析HTML
result = etree.HTML(response.text).xpath(rule)[0]
# 5. 保存结果
print result
if __name__ == '__main__':
main()
经过优化,我们完成了异步版本的爬虫代码。
有了这些基础知识之后,我们看一个完整的例子,如何抓取一个整站数据?
整站爬虫
# coding: utf8
"""整站爬虫"""
from gevent import monkey
monkey.patch_all()
from urlparse import urljoin
import requests
from lxml import etree
from gevent.pool import Pool
from gevent.queue import Queue
base_url = 'http://jshk.com.cn'
# 种子URL
start_url = 'http://jshk.com.cn/tag/?view=type&icn=index-sorttags-all'
# 解析规则
rules = {
# 标签页列表
'list_urls': "//table[@class='tagCol']/tbody/tr/td/a/@href",
# 详情页列表
'detail_urls': "//li[@class='subject-item']/div[@class='info']/h2/a/@href",
# 页码
'page_urls': "//div[@id='subject_list']/div[@class='paginator']/a/@href",
# 书名
'title': "//div[@id='wrapper']/h1/span/text()",
}
# 定义队列
list_queue = Queue()
detail_queue = Queue()
# 定义协程池
pool = Pool(size=10)
def crawl(url):
"""首页"""
response = requests.get(url)
list_urls = etree.HTML(response.text).xpath(rules['list_urls'])
for list_url in list_urls:
list_queue.put(urljoin(base_url, list_url))
def list_loop():
"""采集列表页"""
while True:
list_url = list_queue.get()
pool.spawn(crawl_list_page, list_url)
def detail_loop():
"""采集详情页"""
while True:
detail_url = detail_queue.get()
pool.spawn(crawl_detail_page, detail_url)
def crawl_list_page(list_url):
"""采集列表页"""
html = requests.get(list_url).text
detail_urls = etree.HTML(html).xpath(rules['detail_urls'])
# 详情页
for detail_url in detail_urls:
detail_queue.put(urljoin(base_url, detail_url))
# 下一页
list_urls = etree.HTML(html).xpath(rules['page_urls'])
for list_url in list_urls:
list_queue.put(urljoin(base_url, list_url))
def crawl_detail_page(list_url):
"""采集详情页"""
html = requests.get(list_url).text
title = etree.HTML(html).xpath(rules['title'])[0]
print title
def main():
# 1. 标签页
crawl(start_url)
# 2. 列表页
pool.spawn(list_loop)
# 3. 详情页
pool.spawn(detail_loop)
# 开始采集
pool.join()
if __name__ == '__main__':
main()
我们想要抓取图书的整站数据,执行的流程是:
找到入口,也就是从书籍标签页进入,提取所有标签 URL
进入每个标签页,提取所有列表 URL
进入每个列表页,提取每一页的详情URL和下一页列表 URL
进入每个详情页,拿到书籍信息
如此往复循环,直到数据抓取完毕
这就是抓取一个整站的思路,很简单,无非就是分析我们浏览网站的行为轨迹,用程序来进行自动化的请求、抓取。
理想情况下,我们应该能够拿到整站的数据,但实际情况是,对方网站往往会采取防爬虫措施,在抓取一段时间后,我们的 IP 就会被封禁。