最近发现天涯论坛是一个挺有意思的网站,有各种乱七八糟的帖子足以填补无聊时候的空虚感,但是相当不爽的一件事就是天涯的分页模式下想连贯的把楼主的内容看完实在是太心酸了,一个999页的帖子,百分之九十都是无聊网友的灌水,有时候连续翻几十页才能找到楼主的一条内容。所以无聊之下,就打算写一个简单的爬虫,能一次性把某一个帖子下楼主的所有内容一次性的下载下来。好吧,说了这么多废话,现在开始讲点正事。

网页的地址形式:http://bbs.tianya.cn/post-no05-355576-1.shtml,其中.shtml前的1表示这是当前帖子的第一页,我们可以根据第一页的内容解析出最大的页数,然后遍历的去解析每一个页面,获得楼主的全部言论。

网页的源码简单如下图,每一块内容都放在atl-content这个div中,可以根据下面的一个注释来判断是不是楼主的发言,而正文内容放在bbs-content这个div中,如果有图片的话,里面会有图片的链接,实现的过程中我就是根据这两点找到楼主的言论,并把内容提取出来。

为了爬取的高效性,实现的过程中我利用了python的threading模块,下面是threads.py模块,定义了下载解析页面的线程,下载图片的线程以及线程池

import threading
import urllib2
import Queue
import re

thread_lock = threading.RLock()

#下载页面的一个函数,header中没有任何内容也可以顺利的下载,就省去了
def download_page(html_url):
    try:
        req = urllib2.Request(html_url)
        response = urllib2.urlopen(req)
        page = response.read()
        return page
    except Exception:
        print 'download %s failed' % html_url
        return None

#下载图片的一个方法,和上面的函数很像,只不过添加了一个文件头
#因为在测试的过程中发现天涯对于没有如下文件头的图片链接是不会返回正确的图片的
def download_image(image_url, referer):
    try:
        req = urllib2.Request(image_url)
        req.add_header('Host', 'img3.laibafile.cn')
        req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 6.3; WOW64; rv:33.0) Gecko/20100101 Firefox/33.0')
        req.add_header('Accept', 'image/png,image/*;q=0.8,*/*;q=0.5')
        req.add_header('Accept-Language', 'zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3')
        req.add_header('Referer', referer)
        req.add_header('Origin', 'http://bbs.tianya.cn')
        req.add_header('Connection', 'keep-alive')
        response = urllib2.urlopen(req)
        image = response.read()
        return image
    except Exception:
        print 'download %s failed' % image_url
        return None

#下载和解析一个页面的线程类
class download_html_page(threading.Thread):
    #name:线程的名字
    #page_range:用户输入的页面范围
    #page_contents:解析之后楼主的内容
    #img_urls:解析之后楼主贴的图的链接
    #html_url:输入的页面url
    #first_page:第一次已经下载好的页面,主要是考虑效率,不重复下载
    def __init__(self, name, page_range, page_contents, img_urls, html_url, first_page):
        threading.Thread.__init__(self)
        self.name = name
        self.page_range = page_range
        self.page_contents = page_contents
        self.img_urls = img_urls

self.html_url = html_url
        self.first_page = first_page
    
    #判断是不是楼主的内容
    def is_louzhu(self, s):
        result = re.search(r'<!-- <div class="host-ico">(.*?)</div> -->', s, re.S)
        return (result is not None)

#获得页面里属于楼主图片的url
    def get_img_url(self, s, page_url):
        #判断是不是楼主给其他用户的评论,如果是的话,直接过滤掉(本人从不看评论)
        is_louzhu_answer = re.search(r'-{15,}<br>', s, re.S)
        if is_louzhu_answer is None:
            imgurl = re.findall(r'<img.*?original="(?P<imgurl>.*?)".*?/><br>', s, flags = re.S)

url_path = []
            for one_url in imgurl:
                self.img_urls.put(one_url + '|' + page_url)
                path = re.search('\w+\.jpg', one_url).group(0)
                url_path.append('img/' + path)

segments = re.split(r'<img .*?/><br>', s.strip())
            content = segments[0].strip()
            for i in range(len(url_path)):
                content += '\n<img src = "' + url_path[i] + '" />\n<br>'
                content += segments[i+1].strip()
            return content

#解析夜歌页面
    def parse_page(self, html_page, page_url):
        html_page.decode('utf-8')
        Items = re.findall(r'<div class="atl-content">(?P<islouzhu>.+?)<div class="bbs-content.*?">(?P<content>.+?)</div>', html_page, re.S)
        page_content = ''

for item in Items:
            if self.is_louzhu(item[0]):
                one_div = self.get_img_url(item[1], page_url)
                if one_div is not None:
                    page_content += one_div
        return page_content

def run(self):
        while self.page_range.qsize() > 0:
            page_number = self.page_range.get()
            page_url = re.sub('-(\d+?)\.shtml', '-' + str(page_number) + '.shtml', self.html_url)

page_content = ''
            print 'thread %s is downloading %s' % (self.name, page_url)
            if page_url == self.html_url:
                page_content = self.parse_page(self.first_page, page_url)
            else:
                page = download_page(page_url)
                if page is not None:
                    page_content = self.parse_page(page, page_url)
            #thread_lock.acquire()
            #self.page_contents[page_number] = page_content
            #thread_lock.release()
            self.page_contents.put(page_content, page_number)
        self.img_urls.put('finished')

#下载图片的线程
class fetch_img(threading.Thread):
    def __init__(self, name, img_urls, download_img):
        threading.Thread.__init__(self)
        self.name = name
        self.img_urls = img_urls
        self.download_img = download_img

def run(self):
        while True:
            message = self.img_urls.get().split('|')
            img_url = message[0]
            if img_url == 'finished':
                self.img_urls.put('finished')
                break
            else:
                thread_lock.acquire()
                if img_url in self.download_img:
                    thread_lock.release()
                    continue
                else:
                    thread_lock.release()
                    print 'fetching image %s' % img_url
                    referer = message[1]
                    image = download_image(img_url, referer)
                
                    image_name = re.search('\w+\.jpg', img_url).group(0)
                    with open(r'img\%s' % image_name, 'wb') as img:
                        img.write(image)
                    thread_lock.acquire()
                    self.download_img.add(img_url)
                    thread_lock.release()

#定义了一个线程池
class thread_pool:
    def __init__(self, page_range, page_contents, html_url, first_page):
        self.page_range = page_range
        self.page_contents = page_contents
        self.img_urls = Queue.Queue()
        self.html_url = html_url
        self.first_page = first_page
        self.download_img = set()
        
        self.page_thread_pool = []
        self.image_thread_pool = []
        
    def build_thread(self, page, image):
        for i in range(page):
            t = download_html_page('page thread%d' % i, self.page_range, self.page_contents,
                                    self.img_urls, self.html_url, self.first_page)
            self.page_thread_pool.append(t)
        for i in range(image):
            t = fetch_img('image thread%d' % i, self.img_urls, self.download_img)
            self.image_thread_pool.append(t)
        
    def all_start(self):
        for t in self.page_thread_pool:
            t.start()
        for t in self.image_thread_pool:
            t.start()
    
    def all_join(self):
        for t in self.page_thread_pool:
            t.join()
        for t in self.image_thread_pool:
            t.join()

下面是主线程的代码:

# -*- coding: utf-8 -*-  
import re
import Queue
import threads

if __name__ == '__main__':
    html_url = raw_input('enter the url: ')
    html_page = threads.download_page(html_url)

max_page = 0
    title = ''
    if html_page is not None:
        search_title = re.search(r'<span class="s_title"><span style="\S+?">(?P<title>.+?)</span></span>', html_page, re.S)
        title = search_title.groupdict()['title']

search_page = re.findall(r'<a href="/post-\S+?-\d+?-(?P<page>\d+?)\.shtml">(?P=page)</a>', html_page, re.S)
        for page_number in search_page:
            page_number = int(page_number)
            if page_number > max_page:
                max_page = page_number
                
    print 'title:%s' % title
    print 'max page number: %s' % max_page
    
    start_page = 0
    while start_page < 1 or start_page > max_page:
        start_page = input('input the start page number:')
        
    end_page = 0
    while end_page < start_page or end_page > max_page:
        end_page = input('input the end page number:')
        
    page_range = Queue.Queue()
    for i in range(start_page, end_page + 1):
        page_range.put(i)

page_contents = {}
    thread_pool = threads.thread_pool(page_range, page_contents, html_url, html_page)
    thread_pool.build_thread(1, 1)
    thread_pool.all_start()
    thread_pool.all_join()

运行的时候需要手动在python代码的同级路径下创建img的文件夹,用来存放图片,由于本人比较懒,就没有用python生成这个文件夹,最后下载的结果如下图,如果现实乱码的话,需要把网页的编码格式设置为Unicode

python实现网络爬虫下载天涯论坛帖子的更多相关文章

  1. 利用Python编写网络爬虫下载文章

    #coding: utf-8 #title..href... str0='blabla<a title="<论电影的七个元素>——关于我对电影的一些看法以及<后会无期 ...

  2. Python简单网络爬虫实战—下载论文名称,作者信息(下)

    在Python简单网络爬虫实战—下载论文名称,作者信息(上)中,学会了get到网页内容以及在谷歌浏览器找到了需要提取的内容的数据结构,接下来记录我是如何找到所有author和title的 1.从sou ...

  3. Python即时网络爬虫项目: 内容提取器的定义

    1. 项目背景 在python 即时网络爬虫项目启动说明中我们讨论一个数字:程序员浪费在调测内容提取规则上的时间,从而我们发起了这个项目,把程序员从繁琐的调测规则中解放出来,投入到更高端的数据处理工作 ...

  4. 读书笔记汇总 --- 用Python写网络爬虫

    本系列记录并分享:学习利用Python写网络爬虫的过程. 书目信息 Link 书名: 用Python写网络爬虫 作者: [澳]理查德 劳森(Richard Lawson) 原版名称: web scra ...

  5. Python即时网络爬虫项目: 内容提取器的定义(Python2.7版本)

    1. 项目背景 在Python即时网络爬虫项目启动说明中我们讨论一个数字:程序员浪费在调测内容提取规则上的时间太多了(见上图),从而我们发起了这个项目,把程序员从繁琐的调测规则中解放出来,投入到更高端 ...

  6. Python即时网络爬虫:API说明

    API说明——下载gsExtractor内容提取器 1,接口名称 下载内容提取器 2,接口说明 如果您想编写一个网络爬虫程序,您会发现大部分时间耗费在调测网页内容提取规则上,不讲正则表达式的语法如何怪 ...

  7. Python学习网络爬虫--转

    原文地址:https://github.com/lining0806/PythonSpiderNotes Python学习网络爬虫主要分3个大的版块:抓取,分析,存储 另外,比较常用的爬虫框架Scra ...

  8. Python 3网络爬虫开发实战》中文PDF+源代码+书籍软件包

    Python 3网络爬虫开发实战>中文PDF+源代码+书籍软件包 下载:正在上传请稍后... 本书书籍软件包为本人原创,在这个时间就是金钱的时代,有些软件下起来是很麻烦的,真的可以为你们节省很多 ...

  9. Python 3网络爬虫开发实战中文 书籍软件包(原创)

    Python 3网络爬虫开发实战中文 书籍软件包(原创) 本书书籍软件包为本人原创,想学爬虫的朋友你们的福利来了.软件包包含了该书籍所需的所有软件. 因为软件导致这个文件比较大,所以百度网盘没有加速的 ...

随机推荐

  1. vmware里面的名词 vSphere、vCenter Server、ESXI、vSphere Client

    vmware里面的名词 vSphere.vCenter Server.ESXI.vSphere Client vSphere.vCenter Server.ESXI.vSphere Client VS ...

  2. .NET Core全新路线图

    .NET Core / ASP.NET Core 1 RTM发布两周后,社区也很积极,收到了非常多的反馈,上周五微软的scott Hunter 在dotnet团队官方博客上发布了.NET Core全新 ...

  3. UWP学习目录整理

    UWP学习目录整理 0x00 可以忽略的废话 10月6号靠着半听半猜和文字直播的补充看完了微软的秋季新品发布会,信仰充值成功,对UWP的开发十分感兴趣,打算后面找时间学习一下.谁想到学习的欲望越来越强 ...

  4. async & await 的前世今生(Updated)

    async 和 await 出现在C# 5.0之后,给并行编程带来了不少的方便,特别是当在MVC中的Action也变成async之后,有点开始什么都是async的味道了.但是这也给我们编程埋下了一些隐 ...

  5. ABP教程-打造一个《电话簿项目》-目录-MPA版本-基于ABP1.13版本

    此系列文章会进行不定期的更新,应该会有6章左右. 感兴趣的朋友可以跟着看看,本教程适合已经看过ABP的文档但是又无从下手的小伙伴们. 初衷: 发布系列教程的原因是发现ABP在园子火了很久,但是发现还是 ...

  6. InnoDB体系结构学习笔记

    后台线程 Master Thread 核心的后台线程,主要负责将缓冲池的数据异步刷新到磁盘,保证数据的一致性,包括(脏页的刷新).合并插入缓冲.(UNDO页的回收)等 IO Thread 4个writ ...

  7. C#中5步完成word文档打印的方法

    在日常工作中,我们可能常常需要打印各种文件资料,比如word文档.对于编程员,应用程序中文档的打印是一项非常重要的功能,也一直是一个非常复杂的工作.特别是提到Web打印,这的确会很棘手.一般如果要想选 ...

  8. 微信小程序初探

    做为码农相信大家最近肯定都会听到微信小程序,虽然现阶段还没有正式开放注册,但大家可以还是可以开发测试. 到微信的WIKI(http://mp.weixin.qq.com/wiki?t=resource ...

  9. iOS在导航栏上居中显示分段控件(UISegmentedControl)

    UISegmentedControl *segmentedControl = [[UISegmentedControl alloc] initWithItems:nil]; segmentedCont ...

  10. AFN解析器里的坑

    AFN框架是用来用来发送网络请求的,它的好处是可以自动给你解析JSON数据,还可以发送带参数的请求AFN框架还可以监测当前的网络状态,还支持HTTPS请求,分别对用的类为AFNetworkReacha ...