Python学习之爬虫初体验-定向爬虫-爬取本博客文章

2016/01/11 Python

大概快两周没怎么碰Python了,因为挺忙,之前专周在写C#的程序,然后前些日子做一个游戏的UI,所以就好久没碰咯。其实最大的问题是应该是拖延症晚期的我了吧

先说一下要实现的功能:爬取这个博客的所有文章的内容。虽然挺简单,但是毕竟是第四天学习。所以记录一下。

先说一下理论

一个定向爬虫大概分为以下几个部分

  • URL管理器
  • 网页下载器
  • 网页解析器

URL管理器

用于管理我们需要爬取的URL以及以及爬取过的URL,避免重复爬取以及程序陷入死循环。

1.存储位置可以有

  1. 内存
  2. 关系数据库(如mysql、Oracle等)
  3. 缓存数据库(如 redis)

2.包含的功能

  1. 添加URL
  2. 获取新的URL
  3. 判断是否有为爬取的URL

网页下载器

用于请求指定URL的内容,对于Python通常有两种实现方式

  1. 使用 urllib2 的官方模块
  2. 使用 requests 的第三方的库

后者功能更加强大

网页解析器

用于从下载到网页内容中提取需要的信息。实现方法有:

  1. 正则表达式(模糊匹配)
  2. html.parser
  3. beautifulsoup (第三方库)
  4. ixml (第三方库)

定向爬虫示例-爬取本博客的所有文章

  1. 确定目标-本博客的所有文章
  2. 分析
    • url格式
      打开我的博客 539go.com随便点进去一篇文章,可以看到 URL的格式大致是 http://539go.com/index.php/+类型+/数字.html
      首页往下翻,点next 下一页后的url是
      http://539go.com/index.php/page/2/
      所以URL的类型还应该包括
      http://539go.com/index.php/page/+数字/
      还有什么
      http://539go.com/index.php/+分类/
      http://539go.com/index.php/+标签/
      等等若干。。。
      也就是说http://539go.com/index.php/ 后面加一部分内容,就可以基本包含这个博客上的所有文章,即:
      r”http://539go.com/index.php/\w”
    • 数据格式
      随便打开一篇文章如 http://539go.com/index.php/python/79.html 查看一下源代码或者选中标题右击元素审查可以看到标题为 <h1 class="post-title">Python学习第二天之正则表达式</h1>
      而文章的内容则是在 <div class="post-content"> ... </div> 中的。
      标题和内容的格式在写代码时会用到
    • 网页编码(很重要)
      通常都为UTF-8 当然,我的博客也是UTF-8
  3. 编写代码

    准备

    • 网页下载器这里我使用了 urllib2模块。这是Python官方模块,不需要另外下载插件
    • 解析器部分使用 beautifulSoup 这是第三方库,如果你使用的IDE为PyCharm,可以直接在设置里面搜索beautiful4 下载。其他安装方法自行百度。

spider_main.py main函数、程序入口

# coding:utf8  
import url_manager,html_downloader,html_parser,html_outputer,myparser  
# 入口  
class SpiderMain(object):  
    def __init__(self):  # 初始化  
        # url管理器  
        self.urls = url_manager.UrlManager()        
        # html下载器  
        self.downloader = html_downloader.HtmlDownloader()        
        # html解析器  
        #self.parser = html_parser.HtmlParser()        
        self.parser = myparser.HtmlParser()  
        # 输出  
        self.outputer = html_outputer.HtmlOutputer()  
    #启动爬虫  
    def craw(self,root_url):  
        count = 1#当前爬取url  
        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()  
                #打印当前爬取到的个数以及url  
                print 'craw %d : %s'%(count,new_url)  
                #下载url页面  
                html_cont = self.downloader.download(new_url)  
                #解析下载页面(当前url,怕去到的数据)  
                new_urls,new_data = self.parser.parse(new_url,html_cont)  
                #添加批量url  
                self.urls.add_new_urls(new_urls)  
                #收集新的数据  
                self.outputer.collect_data(new_data)  
                if count==100:  
                    break  
                count+=1  
            except Exception as e:  
                print 'craw failed--',e  
        #输出收集好的数据  
        self.outputer.output_html()  
# 入口 main函数  
if __name__=="__main__":  
    # 入口  
    root_url="http://539go.com"  
    obj_spider = SpiderMain()  
    obj_spider.craw(root_url)  

html_downloader.py 网页下载器

# coding:utf8  
import urllib2  

# 网页下载器  
class HtmlDownloader(object):  
    def download(self,url):  
        if url is None:  
            return None  
        
        response = urllib2.urlopen(url)  
        # 不等于200则请求失败  
        if response.getcode() != 200:  
            return None  
        
        return response.read()  

url_manager.py url管理

# coding:utf8  

# url管理  
class UrlManager(object):  
    def __init__(self):  
        # set表  
        self.new_urls = set()  
        self.old_urls = set()  
    
    #添加单个url  
    def add_new_url(self,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)  
    
    #添加批量urls  
    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):  
        return len(self.new_urls)!=0  
    
    #获取新的url  
    def get_new_url(self):  
        new_url=self.new_urls.pop()  
        self.old_urls.add(new_url)  
        return new_url  

myparser.py 网页解析器

# coding:utf8  
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"http://539go.com/index.php/\w"))  
        for link in links:  
            new_url = link['href']     
            print new_url       
            new_urls.add(new_url)  
        return new_urls  
    
    def _get_new_data(self, page_url, soup):  
        res_data = {}  
        
        #url  
        res_data['url'] = page_url  
        
        title_node=soup.find('h1',class_="post-title")  
        
        try:  
            res_data['title'] = title_node.get_text()  
            summary_node = soup.find("div",class_="post-content")  
            res_data['summary'] = summary_node.get_text()  
        except Exception as e:  
            print '此页无标题',e  
            res_data['title'] = ''  
            res_data['summary'] = ''          
            
        return res_data  
    
    def parse(self,page_url,html_cont):  
        
        if page_url is None or html_cont is None:  
            return 
        
        # 三个参数 1.网页文本 2. 3.编码  
        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  

html_outputer.py 输出到文件

# coding:utf8  

# 数据输出到html  
class HtmlOutputer(object):  
    
    def __init__(self):  
        self.datas = [] #列表  
    
    def collect_data(self,data):  
        if data is None:  
            return  
        # 添加数据  
        self.datas.append(data)  
    
    def output_html(self):  
        # 打开文件。  写 W  
        fout = open('output.html','w')  
        
        fout.write("<html><head><meta charset=\"utf-8\" /><tithtmlle>定向爬虫测试</title></head>")  
        fout.write("<body>")  
        fout.write("<table>")  
        
        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()  

注意:

解析器myparser.py中的代码:

try:  
            res_data['title'] = title_node.get_text()  
            summary_node = soup.find("div",class_="post-content")  
            res_data['summary'] = summary_node.get_text()  
        except Exception as e:  
            print '此页无标题',e  
            res_data['title'] = ''  
            res_data['summary'] = ''          
            
        return res_data  

之所以这么写,是因为博客中的某些网页是没有整篇的文章内容的,比如首页,只是一个导航作用。如果直接get_text()会报错。

  1. 运行

    上面的四个.py的文件,分别为实现了 指定网页的下载、解析、保存
    运行 spider_main.py 后
    运行.jpg

然后会输出一个output.html的网页文件,用浏览器打开。就可以看到整个博客上的所有文章内容了。
不过由于我的博客上的文章全部用markdown书写,而使用beautifulsoup爬取得为文本,如果要用markdown格式输出,需要直接爬取div中的全部内容,这里只是为了学习Python静态爬虫,就不作过多的演示了。
截图.jpg

学习参考 慕课网:Python开发简单爬虫 教程