博主将来想从事数据挖掘算法相关的工作,数据挖掘的前提是有数据可用,于是想学些与爬虫有关的技术。前几天从imooc中学习了Python开发简单爬虫课程(课程网址),功能是爬取百度百科的内容,如果网页上还有相关链接,把相关链接的网页的内容也爬取下来。感觉老师讲的非常好,对于刚接触爬虫的、对面向对象编程有一定了解的同学非常合适,详细程度可以说是手把手教学了。这里记录一下我的学习过程。只是个人理解,说的不对欢迎批评指正。
首先介绍我的Python编程环境:WIN7+eclipse+PyDev+Python2.7 虽然Python已经有了更高版本Python3.5.*但是它相对Python2.7有较大的调整,网上关于3.5的教程也有限。我刚开始的时候就装了3.5,编程时遇到问题去百度,发现很多问题是由2.7和3.5两个版本编程兼容性导致的,在这方面吃了些亏,才重新安装了Python2.7,感觉不错。
下面是学习过程的记录。
爬虫的架构:爬虫调度端,URL管理器,网页下载器,网页解析器,有价值信息输出与存储。
1.爬虫调度端。这部分因该具有的功能有输入要爬取的首页,还有爬虫的整体调度,如启动爬虫、url管理器的启动,网页下载器的启动,网页解析的启动,网页输出、存储等。下面直接看代码。
# coding: utf-8
from baike_spider import html_downloader, url_manager, html_parser, html_outputer
class SpiderMain(object):
def __init__(self):#构造方法
self.urls = url_manager.UrlManager()#初始化URL管理器
self.downloader = html_downloader.HtmlDownloader()#初始化网页下载器
self.parser = html_parser.HtmlParser()#初始化网页解析器
self.outputer = html_outputer.HtmlOutputer()#初始化输出器
def craw(self, root_url):#开始执行爬虫的方法
count = 1#计数器,计数爬取页面的总数量
count2 = 0#计数器,计数爬取失败的网页个数
self.urls.add_new_url(root_url)#传入网页入口
while self.urls.has_new_url():#对网页内包括的连接网页循环抓取,先判断URL管理器不空
try:#有些页面可能失效了,要有异常处理
new_url = self.urls.get_new_url()#获取URL管理器中的一个URL
print "craw %d : %s \n"%(count,new_url)#打印当前爬去的页面
html_cont = self.downloader.download(new_url)#下载该页面为String
new_urls, new_data = self.parser.parse(new_url,html_cont)#把页面解析为新连接和网页数据两部分,其中new_data 中含有当页的链接、title和summary,new_url是当前页面中的所有链接的集合
print new_data["title"]+"\n", new_data["summary"]
self.urls.add_new_urls(new_urls)#新链接存入URL管理器
self.outputer.collect_data(new_data)#网页数据收集
if count == 10:#控制打印页面的数量
break
count = count+1
except Exception,e:
count2 = count2+1
print e
print "craw failed"
self.outputer.output_html()
print str(count-count2)+" successful,"," while "+str(count2)+" failed "
if __name__=="__main__": #主函数
root_url = "http://baike.baidu.com/view/21087.htm" #入口页
obj_spider = SpiderMain()#创建对象
obj_spider.craw(root_url)#启动爬虫
# coding: utf-8
class UrlManager(object):
def __init__(self):
self.new_urls = set()
self.old_urls = set()
def add_new_url(self,url):#一个url的
if url is None:
return#如果没有url就不存了
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):
if urls is None or len(urls) == 0:
return#没有就不添加
for url in urls:
self.add_new_url(url)#调用一个url存储的方法
def has_new_url(self):#判断new_urls中是否还有url
return len(self.new_urls) !=0
def get_new_url(self):#从new_urls中取出一个url,同时把取出的url方法old_urls中
new_url = self.new_urls.pop()
self.old_urls.add(new_url)
return new_url
# coding: utf-8
import urllib2
class HtmlDownloader(object):
def download(self,url):
if url is None:
return None #没给出页面
#request = urllib2.Request(url)
#request.add_header("user-agent", "Mozilla/5.0")#把爬虫伪装为一个浏览器
response = urllib2.urlopen(url)
if response.getcode() != 200:#请求是否成功
return None
return response.read() #返回的是网页的字符串
4.网页解析器。这一部分把下载到的网页用beautifulsoup进行结构化解析,得到网页中的所有关于百科的链接(放在new_urls中)、该网页中的title和summary放在new_data中。代码如下
# coding: utf-8
from bs4 import BeautifulSoup
import re
import urlparse
class HtmlParser(object):
def _get_new_urls(self, page_url, soup):
new_urls = set()
links = soup.find_all("a",href=re.compile(r"/view/\d+\.htm"))#获取所有标签名称为a的链接,这里用re嵌入了正则表达式
for link in links:
new_url = link["href"]#获取不完全链接
new_full_url = urlparse.urljoin(page_url,new_url)#page_url与不完全的url整合成与page_url相似的url
new_urls.add(new_full_url)#完全的url加入到新url集合中
return new_urls
def _get_new_data(self, page_url, soup):
res_data = {}#字典数据类型,里面存放的是键值对。url= ??title = ??就像C中的结构体
#url
res_data["url"] = page_url
#Python
title_node = soup.find("dd",class_="lemmaWgt-lemmaTitle-title").find("h1")#先找到标题为dd的节点,再找大它的子节点中标题为h1的节点
res_data["title"] = title_node.get_text()#获取title内容
#
summary_node = soup.find("div", class_="lemma-summary")#找到包含summary的节点
res_data["summary"] = summary_node.get_text()#获取summary的内容
return res_data #返回数据,包括页面地址,该页面title和summary
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")#建立beautifulsoup对象,进行页面解析
new_urls = self._get_new_urls(page_url,soup)#解析得到的新url
new_data = self._get_new_data(page_url,soup)#解析得到当页的数据
return new_urls, new_data
5.有价值信息的输出与存储。这里把获取的信息存放在了output.html中:
# coding: utf-8
class HtmlOutputer(object):
def __init__(self):
self.datas = []#网页的data数据暂时存放在datas中,最后在写入html文件
def collect_data(self,data):
if data is None:
return
self.datas.append(data)
#ascii是python的默认编码方式,这里要指定编码方式为utf-8
#下面以网页格式输出
def output_html(self):
fout = open("output.txt","w")#打开要写入的文件
fout.write("")#网页格式输出
fout.write("")
fout.write("")
for data in self.datas:
fout.write("")
fout.write("%s "% data["url"])
fout.write("%s "% data["title"].encode("utf-8"))
fout.write("%s "% data["summary"].encode("utf-8"))
fout.write(" ")
fout.write("
")#标签都是成对出现的,结尾标签是/*格式。如,对应结尾是
fout.write("")
fout.write("")
fout.close()#关闭文件
除了记录自己学到的内容,这篇博文还有一个目的,就是希望为和我一样学习了这门课的同学提供一个代码的校对版本。以上代码是我运行过的,完全没问题。如果大家谁的代码运行出错的话,可以过来对比一下,便于更快的找到错误。