如何构建一个通用的Python爬虫平台?
程序IT圈
共 10031字,需浏览 21分钟
·
2021-02-23 13:53
阅读本文大约需要15~20分钟。 本文章内容较多,非常干货!如果手机阅读体验不好,建议先收藏后到 PC 端阅读。
之前做爬虫时,在公司设计开发了一个通用的垂直爬虫平台,后来在公司做了内部的技术分享,这篇文章把整个爬虫平台的设计思路整理了一下,分享给大家。
爬虫简介
网络爬虫(又被称为网页蜘蛛,网络机器人),是一种按照一定的规则,自动地抓取网页信息的程序或者脚本。
通用爬虫(搜索引擎) 垂直爬虫(特定领域)
如何写爬虫
简单爬虫
# coding: utf8
"""简单爬虫"""
import requests
from lxml import etree
def main():
# 1. 定义页面URL和解析规则
crawl_urls = [
'https://book.douban.com/subject/25862578/',
'https://book.douban.com/subject/26698660/',
'https://book.douban.com/subject/2230208/'
]
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,拿到数据 保存数据
异步爬虫
# 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 = [
'https://book.douban.com/subject/25862578/',
'https://book.douban.com/subject/26698660/',
'https://book.douban.com/subject/2230208/'
]
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 = 'https://book.douban.com'
# 种子URL
start_url = 'https://book.douban.com/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 进入每个详情页,拿到书籍信息 如此往复循环,直到数据抓取完毕
防反爬的整站爬虫
# coding: utf8
"""防反爬的整站爬虫"""
from gevent import monkey
monkey.patch_all()
import random
from urlparse import urljoin
import requests
from lxml import etree
import gevent
from gevent.pool import Pool
from gevent.queue import Queue
base_url = 'https://book.douban.com'
# 种子URL
start_url = 'https://book.douban.com/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)
# 定义代理池
proxy_list = [
'118.190.147.92:15524',
'47.92.134.176:17141',
'119.23.32.38:20189',
]
# 定义UserAgent
user_agent_list = [
'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A',
'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; AS; rv:11.0) like Gecko',
]
def fetch(url):
"""发起HTTP请求"""
proxies = random.choice(proxy_list)
user_agent = random.choice(user_agent_list)
headers = {'User-Agent': user_agent}
html = requests.get(url, headers=headers, proxies=proxies).text
return html
def parse(html, rule):
"""解析页面"""
return etree.HTML(html).xpath(rule)
def crawl(url):
"""首页"""
html = fetch(url)
list_urls = parse(html, 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 = fetch(list_url)
detail_urls = parse(html, rules['detail_urls'])
# 详情页
for detail_url in detail_urls:
detail_queue.put(urljoin(base_url, detail_url))
# 下一页
list_urls = parse(html, rules['page_urls'])
for list_url in list_urls:
list_queue.put(urljoin(base_url, list_url))
def crawl_detail_page(list_url):
"""采集详情页"""
html = fetch(list_url)
title = parse(html, 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()
现有问题
爬虫脚本繁多,管理和维护困难 爬虫规则定义零散,可能会重复开发 爬虫都是后台脚本,没有监控 爬虫脚本输出的数据格式不统一,可能是文件,也可能也数据库 业务要想使用爬虫的数据比较困难,没有统一的对接入口
平台架构
配置服务:包括抓取页面配置、解析规则配置、数据清洗配置 采集服务:只专注网页的下载,并配置防爬策略 代理服务:持续提供稳定、可用的代理 IP 清洗服务:针对爬虫采集到的数据进行进一步清洗和规整 数据服务:爬虫数据的展示,以及业务系统对接
详细设计
配置服务
正则解析规则 CSS解析规则 XPATH解析规则
采集服务
支持分布式 配置可视化 可周期采集 支持优先级 任务可监控
pyspider
架构图如下:开发配置解析器,对接配置服务,可以解析配置服务的多种规则模式 spider handler
模块定制爬虫模板,并把爬虫任务进行分类,定义成模板,降低开发成本fetcher
模块新增代理 IP 调度机制,对接代理服务,并增加代理 IP 调度策略result_worker
模块把输出结果定制化,用来对接清洗服务
代理服务
免费代理 付费代理
免费代理
收集代理源 定时采集代理 测试代理 输出可用代理
付费代理
清洗服务
数据服务
数据平台展示 数据推送 数据API
解决的问题
爬虫脚本统一管理、配置可视化 爬虫模板快速生成爬虫代码,降低开发成本 采集进度可监控、易跟踪 采集的数据统一输出 业务系统使用爬虫数据更便捷
爬虫技巧
随机 UserAgent 模拟不同的客户端(github有UserAgent库,非常全面) 随机代理 IP(高匿代理 + 代理调度策略) Cookie池(针对需要登录的采集行为) JavaScript渲染页面(使用无界面浏览器加载网页获取数据) 验证码识别(OCR、机器学习)
以上就是构建一个垂直爬虫平台的设计思路,从最简单的爬虫脚本,到写越来越多的爬虫,到难以维护,再到整个爬虫平台的构建,一步步都是遇到问题解决问题的产物,在我们真正发现核心问题时,解决思路也就不难了。
近期文章:
更多Python技术文,可以关注上面公众号
评论