实战项目:基础爬虫 -- 爬取百度100词条

基础爬虫架构及运行流程

实战项目:基础爬虫 -- 爬取百度100词条_第1张图片
基础爬虫框架主要包括五大模块,分别为爬虫调度器、URL管理器、HTML下载器、HTML解析器、数据存储器。功能分析如下:

  • 爬虫调度器主要负责统筹其他四个模块的协调工作。
  • URL管理器负责管理URL链接,维护已经爬取的URL集合和未爬取的URL集合,提供获取新URL链接的接口。
  • HTML下载器用于从URL管理器中获取未爬取的URL链接并下载HTML网页。
  • HTML解析器用于从HTML下载器中获取已经下载的HTML网页,并从中解析出新的URL链接交给URL管理器,解析出有效数据交给数据存储器。
  • 数据存储器用于将HTML解析器解析出来的数据通过文件或者数据库的形式存储起来。

实战项目:基础爬虫 -- 爬取百度100词条_第2张图片

URL管理器

URL管理器主要包括两个变量,一个是已爬取URL的集合,另一个是未爬取URL的集合。采用Python中的set类型,主要是使用set的去重复功能,防止链接重复爬取,因为爬取链接重复时容易造成死循环。链接去重复在Python爬虫开发中是必备的功能,解决方案主要有三种:1)内存去重;2)关系数据库去重;3)缓存数据库去重。大型成熟的爬虫基本上采用缓存数据库的去重方案,尽可能避免内存大小的限制,又比关系型数据库去重性能高很多。由于基础爬虫的爬取数量较小,因此我们使用Python中set这个内存去重方式。
URL管理器除了具有两个URL集合,还需要提供以下接口,用于配合其他模块使用,接口如下:

  • 判断是否有待爬取的URL,方法定义为has_new_url()。
  • 添加新的URL到未爬取集合中,方法定义为add_new_url(url),add_new_urls(urls)。
  • 获取一个未爬取的URL,方法定义为get_new_url()。
  • 获取未爬取URL集合的大小,方法定义为new_url_size()。
  • 获取已经爬取的URL集合的大小,方法定义为old_url_size()。
    程序URLManager.py的完整代码如下:
# coding:utf-8
class UrlManager(object):
	def __init__(self):
		self.new_urls = set() # 未爬取URL集合
		self.old_urls = set() # 已爬取URL集合
	
	def has_new_url(self):
		'''
		判断是否有未爬取的URL
		:return:
		'''
		return self.new_url_size() != 0
		
	def get_new_url(self):
		'''
		获取一个未爬取的URL
		:return:
		'''
		new_url = self.new_urls.pop()
		self.old_urls.add(new_url)
		return new_url
		
	def add_new_url(self, url):
		'''
		将新的URL添加到未爬取的URL集合中
		:param url:单个URL
		:return:
		'''
		if url is None:
			return
		if url not in self.new_urls and url not in self.old_urls:
			self.new_urls.add(url)
			
	def add_new_urls(self, urls):
		'''
		将新的URL添加到未爬取的URL集合中
		:param urls:url集合
		:return:
		'''
		if urls is None or len(urls) == 0:
			return
		for url in urls:
			self.add_new_url(url)
		
	def new_url_size(self):
		'''
		:return:
		'''
		return len(self.new_urls)
		
	def old_url_size(self):
		'''
		获取已经爬取URL集合的大小
		:return:
		'''
		return len(self.old_urls())

HTML下载器

HTML下载器用来下载网页,这是很需要注意网页的编码,以保证下载的网页没有乱码。下载器需要用到Requests模块,里面只需要实现一个接口即可:download(url)。程序HtmlDownloaders.py代码如下:

# coding:utf-8
import requests
class HtmlDownloader(object):
	
	def download(self, url):
		if url is None:
			return None
		user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
		headers = {'User-Agent':user_agent}
		r = requests.get(url, headers=headers)
		if r.status_code == 200:
			r.encoding = 'utf-8'
			return r.text
		return None

HTML解析器

HTML解析器使用BeautifulSoup4进行HTML解析。需要解析的部分主要分为提取相关词条页面的URL和提取当前词条的标题和摘要信息。
查看百度百科页的源码,可以看到标题的标记位于< dd class=“lemmaWgt-lemmaTitle-title” >< h1 >< /h1 >,摘要文本位于< div class=“lemma-summary” label-module=“lemmaSummary” >。
最后分析一下需要抽取的URL的格式。相关词条的URL格式类似于< a target="_blank" href="/view/7833.htm" >万维网< /a>这种形式,提取出a标记中的href属性即可,从格式中可以看到href属性值是一个相对网址,可以使用urlparse.urljoin函数将当前网址和相对网址拼接完整的URL路径。
HTML解析器主要提供一个parser对外接口,输入参数为当前页面的URL和HTML下载器返回的网页内容。解析器HtmlParser.py程序的代码如下:

# coding:utf-8
import re
import urlparse
from bs4 import BeautifulSoup


class HtmlParser(object):
	
	def parser(self, page_url, html_cont):
		'''
		用于解析网页内容,抽取URL和数据
		:param page_url:下载页面的URL
		:param html_cont:下载的网页内容
		:return:返回URL和数据
		'''
		if page_url is None or html_cont is None:
			return
		soup = BeautifulSoup(html_cont, 'html.parser', from_encoding='utf-8')
		new_urls = self._get_new_urls(page_url, soup)
		new_data = self._get_new_data(page_url, soup)
		return new_urls, new_data
		
		
	def _get_new_urls(self, page_url, soup):
		'''
		抽取新的URL集合
		:param page_url:下载页面的URL
		:param soup:soup
		:return:返回新的URL集合
		'''
		new_urls = set()
		# 抽取符合要求的a标记
		links = soup.find_all('a', href=re.compile(r'/view/\d+\.htm'))
		for link in links:
			# 提取href属性
			new_url = link['href']
			# 拼接成完整网址
			new_full_url = urlparse.urljoin(page_url, new_url)
			new_urls.add(new_full_url)
		return new_urls
		
	def _get_new_data(self, page_url, soup):
		'''
		抽取有效数据
		:param page_url:下载页面的URL
		:param soup:soup
		:return:返回有效数据
		'''
		data = {}
		data['url'] = page_url
		title = soup.find('dd', class_='lemmaWgt-lemmaTitle-title').find('h1')
		data['title'] = title.get_text()
		summary = soup.find('div', class_='lemma-summary')
		# 获取tag中包含的所有文本内容,包括子孙tag中的内容,并将结果作为Unicode字符串返回
		data['summary'] = summary.get_text()
		
		return data

数据存储器

数据存储器主要包括两个方法:store_data(data)用于将解析出来的数据存储到内存中,output_html()用于将存储的数据输出为指定的文件格式,我们使用的是将数据输出为HTML格式。DataOutput.py程序如下:

# coding:utf-8
import codecs

class DataOutput(object):
	
	def __init_(self):
		self.datas = []
	
	def store_data(self, data):
		if data is None:
			return
		self.datas.append(data)
		
	def output_html(self):
		fout = codecs.open('baike.html', 'w', encoding='utf-8')
		fout.write("")
		fout.write("")
		fout.write("")for data in self.datas:
			fout.write("")
			fout.write(""%data['url'])
			fout.write(""%data['title'])
			fout.write(""%data['summary'])
			fout.write("")
			self.datas.remove(data)
		fout.write("
%s%s%s
"
) fout.write("") fout.write("") fout.close()

爬虫调度器

以上对URL管理器、HTML下载器、HTML解析器和数据存储器等模块进行了实现,接下来编写爬虫调度器以协调管理这些模块。爬虫调度器首先要做的是初始化各个模块,然后通过crawl(root_url)方法传入入口URL,方法内部实现按照运行流程控制各个模块的工作。爬虫调度器SpiderMan.py的程序如下:

# coding:utf-8
from firstSpider.DataOutput import DataOutput
from firstSpider.HtmlDownloader import HtmlDownloader
from firstSpider.HtmlParser import HtmlParser
from firstSpider.UrlManager import UrlManager

class SpiderMan(object):
	
	def __init__(self):
		self.manager = UrlManager()
		self.downloader = HtmlDownloader()
		self.parser = HtmlParser()
		self.output = DataOutput()
	
	def crawl(self, root_url):
		# 添加入口URL
		self.manager.add_new_url(root_url)
		# 判断url管理器中是否有新的url,同时判断抓取了多少个url
		while(self.manager.has_new_url() and self.manager.old_url_size() < 100:
			try:
				# 从URL管理器获取新的url
				new_url = self.manager.get_new_url()
				# HTML下载器下载网页
				html = self.downloader.download(new_url)
				# HTML解析器抽取网页数据
				new_urls, data = self.parser.parser(new_url, html)
				# 将抽取的url添加到URL管理器中
				self.manager.add_new_urls(new_urls)
				# 数据存储器存储文件
				self.output.store_data(data)
				print("已经抓取%s个链接" % self.manager.old_url_size())
			except Exception,e:
				print("crawl failed")
			# 数据存储器将文件输出成指定格式
		self.output.output_html()
	
if __name__ == "__main__":
	spider_man = SpiderMan()
	spider_man.crawl("http://baike.baidu.com/view/284853.htm")

你可能感兴趣的:(Python3爬虫学习笔记)