前言:面向对象思想开发一个简单爬虫(Python2.7)
- 开发爬虫需要正则表达式基础,如果要看最简单的爬虫,请移步 Python学习笔记之正则表达式 末尾的实例!
爬虫简单架构:
- 爬虫调度器:启动爬虫,停止爬虫,监视爬虫的运行情况
- URL 管理器:对将要爬取的 URL 和已经爬取的 URL 的管理
- 网页下载器:将指定的网页以 HTML 的形式下载并存储成字符串
- 网页分析器:一方面从网页解析出也价值的数据, 另一方面解析出新的 URL 补充进 URL 管理器
URL 管理器作用:
- 防止重复抓取和循环抓取
URL 管理器功能:
- 添加新 URL 到待爬取集合
- 判断 URL 是否在容器中(容器包含:待爬取,已爬取)
- 判断是否还有待爬取 URL
- 获取待爬取 URL
- 将 URL 从待爬取移动到已爬取
URL 管理器实现方式:
- 内存:待爬取URL集合,已爬取URL集合,采用 set() 数据结构:可避免重复
- 关系数据库:urls(url, is_crawled) urls表,url 待爬取,is_crawled 已爬取字段
- 缓存数据库:待爬取 URL 集合,已爬取 URL 集合,采用 set() 数据结构:可避免重复, 大公司因为高性能把 URL 存储在缓存数据库
网页下载器(爬虫核心组件):
- urllib2 官方模块
- requests 第三方强大插件
网页下载器下载网页三种方法:
1. 直接请求:
response = urllib2.urlopen(url)
code = response.getcode() # 获取状态码,200 表示获取成功
cont = response.read() # 读取内容
2. 添加data,http header
request = urllib2.Request(url) # 创建 Request 对象
request.add_data('user', '401575264') # 添加数据
request.add_header('User-Agent', 'Mozilla/5.0') # 添加 http 的 header
3. 添加特殊情景的处理器,如 cookie
import urllib2, cookielib
cj = cookielib.CookieJar() # 创建 cookie 容器
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) # 创建 opener
urllib2.install_opener(opener) # 给 urllib2 安装 opener 来增强处理器
response = urllib2.urlopen(url) # 用带有 cookie 的 urllib2 访问网页
网页解析器:
- 正则表达式
- html.parser #python自带模块
- Beatiful Soup #第三方插件
- lxml #第三方插件
/* 1 为模糊匹配,234 为结构化解析-DOM (Document Object Modle)树 */
Beautiful Soup语法:
1. 导入模块:from bs4 import BeautifulSoup
2. 创建 BeautifulSoup 对象:
soup = BeautifulSoup(html_doc, 'html.parser', from_encoding='utf-8')
# HTML 文档字符串, HTML 解析器, HTML 文档编码
3. 搜索节点 (find_all, find):
# 查找所有标签为 a 的节点
node_list = soup.find_all('a')
# 查找所有标签为 a ,链接符合 /view/123.html 形式的节点,第二个参数节点属性用正则表达式匹配
node_list = soup.find_all('a', href='/view/123.html')
#查找所有标签为 div, class为abc, 文字为Python的节点
node_list = soup.find_all('div',class_='abc', string='Python')
4. 访问节点信息
node.name # 获取查找到的结点的标签名称
node['href'] # 获取查找到的 a 节点的 href 属性
node.get_text() # 获取查找到的 a 节点的链接文字
实例:
- 爬虫调度器 spider_main.py
# -*- coding: utf-8 -*-
import url_manager, html_downloader, html_parser, html_outputer
class SpiderMain(object):
#初始化各个模块
def __init__(self):
#url管理器
self.urls = url_manager.UrlManager()
#网页下载器
self.downloader = html_downloader.HtmlDownloader()
#网页解析器
self.parser = html_parser.HtmlParser()
#数据输出器
self.outputer = html_outputer.HtmlOutputer()
#爬虫调度程序
def craw(self, root_url):
#记录爬取第i个url
count = 1
#添加入口url到待爬取url管理器urls
self.urls.add_new_url(root_url)
#当url管理器有待爬取url
while self.urls.has_new_url():
#异常处理:有些链接可能已经无法访问等
try:
#从待爬取urls中获取一个url
new_url = self.urls.get_new_url()
print 'craw %d : %s' %(count, new_url)
#启动下载器下载网页并保存到html_cont
html_cont = self.downloader.download(new_url)
#启动解析器解析下载好的网页, 并得到新的url列表和新的数据
new_urls, new_data = self.parser.parse(new_url, html_cont)
#将新的url补充到待爬取url管理器urls
self.urls.add_new_urls(new_urls)
#收集数据
self.outputer.collect_data(new_data)
#如果爬取达到100个url就停止爬取
if count == 100:
break
#每爬取一个url, 计数加1
count += 1
except:
print 'craw failed'
#输出收集好的数据
self.outputer.output_html()
if __name__=="__main__":
#入口 URL
root_url = "https://baike.baidu.com/item/Python/407313"
#实例化爬虫对象
obj_spider = SpiderMain()
#爬取网页并处理数据
obj_spider.craw(root_url)
- URL 管理器 url_manager.py
# -*- coding: utf-8 -*-
class UrlManager(object):
def __init__(self):
#待爬取url管理器
self.new_urls = set()
#已爬取url管理器
self.old_urls = set()
#向url管理器中添加一个新的url
def add_new_url(self, url):
if url is None:
return
if url not in self.new_urls and url not in self.old_urls:
self.new_urls.add(url)
#向url管理器中添加批量url
def add_new_urls(self, urls):
if urls is None or len(urls) == 0:
return 0
for url in urls:
self.add_new_url(url)
#判断url管理器中是否有新的带爬取的url
def has_new_url(self):
return len(self.new_urls) != 0
#从url管理器中获取一个新的待爬取url
def get_new_url(self):
#pop会从列表中删除一个元素, 并返回那个元素
new_url = self.new_urls.pop()
self.old_urls.add(new_url)
return new_url
- 网页下载器 htnl_downloader.py
# -*- coding: utf-8 -*-
import urllib2
class HtmlDownloader(object):
def download(self, url):
if url is None:
return None
self.response = urllib2.urlopen(url)
#if response.getcode() != 200:
#return None
return self.response.read()
- 网页分析器 html_parser.py
# -*- coding: utf-8 -*-
from bs4 import BeautifulSoup
import re
class HtmlParser(object):
def _get_new_urls(self, page_url, soup):
#正则表达式获取所有符合格式的链接地址
links = soup.find_all('a',href=re.compile(r'/item/[A-Z0-9%/]+'))
#链接不完整时要补上
new_links = set(map(lambda x: 'https://baike.baidu.com'+x['href'], links))
return new_links
def _get_new_data(self, page_url, soup):
res_data = {}
res_data['url'] = page_url
#获取标题节点:<dd class="lemmaWgt-lemmaTitle-title"><h1 >Python</h1>
title_node = soup.find('dd', class_='lemmaWgt-lemmaTitle-title').find('h1')
#获取标题
res_data['title'] = title_node.get_text()
#获取简介节点:<div class="lemma-summary" label-module="lemmaSummary">
summary_node = soup.find('div', class_="lemma-summary")
res_data['summary'] = summary_node.get_text()
return res_data
def parse(self, page_url, html_cont):
if page_url is None or html_cont is None:
return
soup = BeautifulSoup(html_cont, 'html.parser', from_encoding='utf-8')
#获取新url列表
new_urls = self._get_new_urls(page_url, soup)
#获取新数据
new_data = self._get_new_data(page_url, soup)
return new_urls, new_data
- 数据输出器 html_outputer.py
# -*- coding: utf-8 -*-
class HtmlOutputer(object):
def __init__(self):
self.datas = []
def collect_data(self, data):
if data is None:
return
self.datas.append(data)
#将数据写入一个 HTML 文件
def output_html(self):
f = open('output.html', 'w')
f.write('<html>')
f.write('<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />')
f.write('<body>')
for data in self.datas:
f.write('<table border="1" style="BORDER-COLLAPSE: collapse">')
f.write('<tr>')
f.write('<td>%s</td>' %data['url'])
f.write('<td>%s</td>' %data['title'].encode('utf-8'))
f.write('<td>%s</td>' %data['summary'].encode('utf-8'))
f.write('</tr>')
f.write('</table>')
f.write('<br/>')
f.write('</body>')
f.write('</html>')
f.close()