美文网首页工具教程测试Python 爬虫
Python爬虫初学(二)—— 爬百度贴吧小说和图片

Python爬虫初学(二)—— 爬百度贴吧小说和图片

作者: sunhaiyu | 来源:发表于2016-08-16 19:07 被阅读1133次

    昨天初步接触了爬虫,实现了爬取网络段子并逐条阅读等功能,详见Python爬虫初学(一)。 今天准备对百度贴吧下手了,嘿嘿。依然是跟着这个博客学习的,这次仿照该博主用类的方式写。

    其实我从来不玩贴吧,不过据我所知贴吧有一些网友,他们开帖子连载原创小说;还有些网友提供“福利”,造福广大网民。嗯,所以今天的目标是这样的:

    • 把分散的连载小说下载到本地
    • 批量下载贴吧图片

    一. 下载小说

    1. 定义一个类

    这次用类来写。实现这个也不难,经过昨天的学习已经有一定经验了。导入库什么的就不说了。先看贴吧的url构成,如http://tieba.baidu.com/p/4723863270?see_lz=1&pn=2。其中http://tieba.baidu.com/p/4723863270为该帖的基础地址,?see_lz=1是只看楼主标志位,为1是表示“只看楼主”,pn=2代表当前帖子的页码。现在来定义一个爬取百度贴吧的SpiderBaidu,初始化,然后定义一个open_url()来返回网页内容。

    class SpiderBaidu:
        # 初始化帖子原地址,默认只看楼主
        def __init__(self, url, see_lz_flag=1):
            self.url = url
            # 可设置看所有楼
            self.see_lz = '?see_lz=' + str(see_lz_flag)
            self.res = []
            
        # 打开具体网址并返回网页内容
        def open_url(self, num):
            # 该帖具体网址,num指定页码
            wanted_page = self.url + self.see_lz + '&pn=' + str(num)
            req = request.Request(wanted_page)
            req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 '
                                         '(KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36')
            response = request.urlopen(req)
            html = response.read().decode('utf-8')
            return html
    

    2. 获取标题和总页数

    我们想要知道帖子标题以及总页数,提取出来就是了!

    可以发现标题<h1 class="core_title_txt(...)</h1>包含起来了,这里要注意的是,有些帖子不是<h1>,可能是<h3>或者其他,一会儿匹配的时候考虑进去

        # 提取标题
        def get_title(self):
            # 第1页就包含标题,所以num用1即可
            html = self.open_url(1)
            # 提取标题的规则,<h\d>即无论h1还是h3都会匹配成功
            title_pattern = re.compile(r'<h\d class="core_title_txt.*?>(.*?)</h\d>')
            title = re.findall(title_pattern, html)
            # 返回的是列表且只有一个元素,故用title[0]
            return title[0]
    

    接下来是总页数,仔细观察总页数其实在最上面和最下面都是有一个的,所以一会儿匹配后返回的列表会有两个元素,这两个元素是一模一样的!


    如上图,数字7被<span class="red">(需要提取的数字)</span>包含。代码如下。
        # 获取总页数
        def get_page_num(self): 
            # 第1页也有总页数
            html = self.open_url(1)
            num_pattern = re.compile(r'<span class="red">(\d+)</span>')
            page_num = re.findall(num_pattern, html)
            # 贴吧的最上和最下面都有总页码,随便返回一个即可
            return page_num[0]
    

    我们来看一下提取出来的标题和页码。

    3. 获取正文

    正文前面有空格,依然要用\s+匹配。正文被<div id="post_content...class=d_post_content j_d_post_content...空格空格(正文)</div>包含。以下函数提取出正文。

        # 获取正文
        def get_content(self, num):
            # 获取网页全部内容
            html = self.open_url(num)
            # 提取每楼发言
            content_pattern = re.compile(r'<div id="post_content.*?class="d_post_content j_d_post_content'
                                         r'.*?>\s+(.*?)</div>')
            content = re.findall(content_pattern, html)
            return content
    

    即使提取出帖子正文了,也别高兴的太早。贴吧发帖不可能人人都发的纯文本,可以预想到里面会有图片(包含表情),超链接,还有设置的签名等。这些还没有被过滤掉。(不好意思忘了截图,反正打印出来的内容会含有很多又长又难看的链接)

    我们再制定规则过滤掉。

        # 这里参数con为get_content()函数返回的包含正文的列表
        def get_words_only(self, con):
            for i in con:
                # 删除图片
                each = re.sub(r'<img class=".*?>', '', i)
                # 删除签名
                each = re.sub(r'<div class="post_bubble_top".*?>', '', each)
                # 换行
                each = re.sub(r'<br>', '\n', each)
                # 删除超链接
                each = re.sub(r'<a href=.*?</a>', '', each)
                # 添加到初始化的列表中
                self.res.append(each)
            return self.res
    

    4. 下载小说到本地

    默认模式为只看楼主,其他人插楼小说还咋读是不。

        # 下载到本地
        def save_text(self):
            # 返回的帖子标题作为文件名
            file_title = self.get_title()
            # 最大页码
            page_num = int(self.get_page_num())
            with open(file_title + '.txt', 'w', encoding='utf-8') as f:
                # 每一页内容都写入文件
                for number in range(1, page_num + 1):
                    con = self.get_content(number)
                    # 只留下纯文字,过滤图片、超链接等
                    result = self.get_words_only(con)
                    f.writelines(result)
    

    最后创建一个实例就好了,试试下载吧。

    if __name__ == '__main__':
        spider = SpiderBaidu('http://tieba.baidu.com/p/4698209454')
        title = spider.get_title()
        total_num = spider.get_page_num()
        print('{}(共{}页)'.format(title, total_num))
        spider.save_text()
    

    下载下来后是这个效果,还行,能读。

    二、 批量下载图片

    刚才有过滤图片是不?我们反过来利用它,分分钟就搞定!


    提取图片链接即可。它被<img class="BDE_Image" src="(.*?jpg)"这样的形式包含。
    # 只保存图片
        def save_images(self, folder):
            page_num = int(self.get_page_num())
            # 文件名序号
            seq = 1
            # 创建文件夹
            os.mkdir(folder)
            # 工程目录切换到当成文件夹
            os.chdir(folder)
            for number in range(1, page_num + 1):
                # 网页全部内容
                html = self.open_url(number)
                img_pattern = re.compile(r'<img class="BDE_Image" src="(.*?jpg)"')
                images = re.findall(img_pattern, html)
                # 每爬一页,休息10秒
                time.sleep(10)
                for each in images:
                    # 文件名
                    filename = str(seq) + '.jpg'
                    # 下载到文件夹
                    request.urlretrieve(each, filename)
                    # 数字递增方式给文件命名
                    seq += 1
                    # 每两秒下载一次
                    time.sleep(2)
    

    可以适当加入time.sleep(),防止访问频率过快导致爬虫封IP。简单的可以这么做,当然可以用代理,多线程,不过我还没接触到,以后再深入。

    居然几百张!大丰收呀,看到图片自动地就被飞速下载到本地了,还用一张张右键吗?No!挂着程序让它跑,看部电影去吧!

    整理一下,全部代码如下

    from urllib import request, parse
    import re
    import os
    import time
    
    
    class SpiderBaidu:
        # 初始化帖子原地址,默认只看楼主为否
        def __init__(self, url, see_lz_flag=1):
            self.url = url
            self.see_lz = '?see_lz=' + str(see_lz_flag)
            self.res = []
    
        def open_url(self, num):
            # 该帖具体网址
            wanted_page = self.url + self.see_lz + '&pn=' + str(num)
            req = request.Request(wanted_page)
            req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 '
                                         '(KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36')
            response = request.urlopen(req)
            html = response.read().decode('utf-8')
            return html
    
        # 提取标题
        def get_title(self):
            html = self.open_url(1)
            title_pattern = re.compile(r'<h\d class="core_title_txt.*?>(.*?)</h\d>')
            title = re.findall(title_pattern, html)
            return title[0]
    
        # 获取总页数
        def get_page_num(self):
            html = self.open_url(1)
            num_pattern = re.compile(r'<span class="red">(\d+)</span>')
            page_num = re.findall(num_pattern, html)
            # 贴吧的最上和最下面都有总页码,随便返回一个即可
            return page_num[0]
    
        # 获取正文
        def get_content(self, num):
            html = self.open_url(num)
            content_pattern = re.compile(r'<div id="post_content.*?class="d_post_content j_d_post_content'
                                         r'.*?>\s+(.*?)</div>')
            content = re.findall(content_pattern, html)
            return content
    
        # 去除文字外所有内容
        def get_words_only(self, con):
            for i in con:
                # 删除图片
                each = re.sub(r'<img class=".*?>', '', i)
                # 删除签名
                each = re.sub(r'<div class="post_bubble_top".*?>', '', each)
                # 换行
                each = re.sub(r'<br>', '\n', each)
                # 删除超链接
                each = re.sub(r'<a href=.*?</a>', '', each)
                self.res.append(each)
            return self.res
    
        # 下载到本地
        def save_text(self):
            # 帖子标题作为文件名
            file_title = self.get_title()
            # 最大页码
            page_num = int(self.get_page_num())
            with open(file_title + '.txt', 'w', encoding='utf-8') as f:
                for number in range(1, page_num + 1):
                    con = self.get_content(number)
                    result = self.get_words_only(con)
                    f.writelines(result)
    
        # 只保存图片
        def save_images(self, folder):
            page_num = int(self.get_page_num())
            # 文件名序号
            seq = 1
            os.mkdir(folder)
            os.chdir(folder)
            for number in range(1, page_num + 1):
                html = self.open_url(number)
                img_pattern = re.compile(r'<img class="BDE_Image" src="(.*?jpg)"')
                images = re.findall(img_pattern, html)
                time.sleep(10)
                for each in images:
                    filename = str(seq) + '.jpg'
                    request.urlretrieve(each, filename)
                    seq += 1
                    time.sleep(2)
    
    if __name__ == '__main__':
        spider = SpiderBaidu('http://tieba.baidu.com/p/4698209454')
        title = spider.get_title()
        total_num = spider.get_page_num()
        print('{}(共{}页)'.format(title, total_num))
        spider.save_text()
        spider.save_images('图')
    

    by @sunhaiyu

    2016.8.16

    相关文章

      网友评论

      本文标题:Python爬虫初学(二)—— 爬百度贴吧小说和图片

      本文链接:https://www.haomeiwen.com/subject/udwpsttx.html