爬虫接触了也有段时间,跟着网上的一些教程,不仅做出了一些实用的小工具,而且对于使用Python爬虫的整个流程有了大致的了解,也知道了爬虫是怎么回事。以前做的一些小的试验,陆续也都会写成博客,今天记录的, 是我在慕课网上(http://www.imooc.com/learn/563)学到的一个爬虫框架,结构清晰合理,很值得学习,这里实现的只是爬虫最简单的功能,不涉及用户的登陆和Cookie验证,当然,这些都是可以扩充到这个框架里的。

首先,先讲一讲整个爬虫框架的思路。

先确定任务:就是以百度百科Python词条为根,陆续爬下相关的1000个网页,这个阈值自然是可以调整的。都知道,在百度百科页面,很多关键词都是有链接的,链接到另一个网页上,可以搜索到更多的链接,就这样循环爬取,直到1000个为止。

代码大概用了150多行,写成了五个对象,SpiderMain、UrlManger、HtmlDownloader、HtmlParser和HtmlOutputer。SpiderMain是所有其余对象的入口,UrlManger负责管理url,用了两个set(),一新一旧,因为已经使用过的链接就没有必要再爬一次了,就放在old里,从网页中解析出来的新的链接放在new里。HtmlDownloder负责将网站的数据下载到本地,HtmlParser负责把下载到本地的网页数据里的相关数据解析出来,这里解析的,一个是链接,另一个是词条的summary,HtmlOupter就负责最后结果的输出。整个框架对应的是一个线性的流程,结构清晰。

下面看代码。

# 实例:爬取python相关一千个网址

 

# 先导入有关的库,这一步很重要

import urllib2

from bs4 import BeautifulSoup # 很好用的解析器

import re # 很容易遗漏,即使漏掉程序也不报错,很隐蔽

 

# 程序的入口类

class SpiderMain(object):

# 初始化对象

def __init__(self):

self.urls = UrlManger()

self.downloader = HtmlDownloader()

self.parser = HtmlParser()

self.outputer = HtmlOutputer()

 

# 最核心的方法

def craw(self, root_url):

count = 1 # 方便计数

self.urls.add_new_url(root_url)

while self.urls.has_new_url():

try:

# 取出url

new_url = self.urls.get_new_url()

print 'craw %d : %s' % (count, new_url)

# 根据url下载网页内容

html_cont = self.downloader.download(new_url)

# 将网页内容解析为需要的数据

new_urls, new_data = self.parser.parse(new_url, html_cont)

# 将数据各自存放

self.urls.add_new_urls(new_urls)

self.outputer.collect_data(new_data)

 

if count == 10:

break

 

count += 1

 

except:

print 'craw failed'

 

# 将数据输出为html文件

self.outputer.output_html()

 

 

 

class UrlManger(object):

 

# 初始化,两个set(),因为set()里的数据不会重复

def __init__(self):

self.new_urls = set()

self.old_urls = set()

 

# 添加一个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,循环使用add_new_url,很巧妙

def add_new_urls(self, urls):

if urls is None or len(urls) == 0:

return

for url in urls:

self.add_new_url(url)

 

# 判断是否还有没有爬取的url,返回bool值

def has_new_url(self):

return len(self.new_urls) != 0

 

# 获取一个url

def get_new_url(self):

new_url = self.new_urls.pop() # pop用的极好,不仅取得了数据,而且将元素从set()中删除

self.old_urls.add(new_url) # 使用过的url要放在old_urls里,避免重复爬取

return new_url

 

# 下载器

class HtmlDownloader(object):

 

def download(self, url):

if url is None:

return None

 

# Python自带的urllib2下载模块

response = urllib2.urlopen(url)

 

# 可以用getcode()方法判断是否下载成功

if response.getcode() != 200:

return None

 

return response.read()

 

# Html解析器,核心,倒着看

class HtmlParser(object):

 

def get_new_urls(self, soup):

 

# 解析出来的url也放在一个set()里

new_urls = set()

# 正则表达式是根据网页源代码分析出来的

links = soup.find_all('a', href=re.compile(r'/view/\d+\.htm'))

for link in links:

new_url = link['href']

# 解析出来的链接是个相对url,要合成成一个绝对url

page_url = 'http://baike.baidu.com'

new_full_url = page_url + new_url

new_urls.add(new_full_url)

return new_urls

 

def get_new_data(self, page_url, soup):

 

# 先构造一个字典

res_data = {}

 

# url

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

 

# BeautifulSoup固定用法,先构造一个BeautifulSoup对象

soup = BeautifulSoup(html_cont, 'html.parser', from_encoding='utf-8')

 

# 方法里面嵌套方法,值得学习

new_urls = self.get_new_urls(soup)

new_data = self.get_new_data(page_url, soup)

return new_urls, new_data

 

# 输出器

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):

# 以写的形式打开文件

fout = open('D:/Learn/Code/python/pachong/output.html', 'w')

 

# 这一句必须加上,否则中文字符会显示乱码

fout.write('<meta charset=utf-8" />')

fout.write('<html>')

fout.write('<body>')

fout.write('<table>')

fout.write('<th>URL</th>')

fout.write('<th>Title</th>')

fout.write('<th>Summary</th>')

 

for data in self.datas:

fout.write('<tr>')

fout.write('<td>%s</td>' % data['url'])

# 注意中文字符的编码

fout.write('<td>%s</td>' % data['title'].encode('utf-8'))

fout.write('<td>%s</td>' % data['summary'].encode('utf-8'))

fout.write('</tr>')

 

fout.write('</table>')

fout.write('</body>')

fout.write('</html>')

 

# 不要忘记关闭文件

fout.close()

 

# 初始化程序

root_url = 'http://baike.baidu.com/view/21087.htm'

obj_spider = SpiderMain()

obj_spider.craw(root_url)

结果如下:

Python爬虫学习笔记(二)

打开output.html

Python爬虫学习笔记(二)

没什么问题!

 

总结:虽然名为一个简易的爬虫框架,其实已经不简单,难得的是思路如此清晰,结构很严谨,很多细节都很值得玩味。就凭这一个项目,也能看得出自己与高手之间的差距。从这些代码中,我领悟到,在写代码之前,一定要先理清思路;其次,这里用到了面向对象(oop)的方式,功能各自独立,又互相补充,这不就是高内聚、低耦合吗?要学的还有很多!

 

源代码地址:https://github.com/Lucifer25/Learn-Python/blob/master/Web-Crawler/SimpleFramework.py

 

参考资料:http://www.imooc.com/learn/563