相对来说,贴吧还是比较好爬一些的,所以就先拿贴吧为例,来做第一个实战。

1 爬前分析

如果要爬取一个网站的内容,我们要先做一般有以下几个步骤:

  • 对url进行分析,找到有规律的内容,定义相应的变量;
  • 开始爬取内容,对获取的内容进行查看;
  • 通过查看,设定正则规则,过滤无用内容;
  • 保存我们需要的内容到文件。

2 url分析

因为上大学时候就很迷恋盗墓笔记,一直关注着,所以这次就爬盗墓笔记吧的内容吧,。

2.1 url分段

盗墓笔记吧的一个帖子网址如下:https://tieba.baidu.com/p/4366865181?see_lz=1&pn=1

https:// # 使用的传输协议 tieba.baidu.com # 是百度的二级域名,指向百度贴吧的服务器。 /p/4366865181 # 服务器的路径 see_lz=1和pn=1分别表示只看楼主和页数 所以,url部分我们就分位两部分,基础部分和参数部分,结合到具体例子可以指定 基础部分:https://tieba.baidu.com/p/4366865181 参数部分:see_lz=1&pn=1

2.2 抓取页面

这里就用到了前面讲的urllib模块,下面直接看代码:

# -*- coding: utf-8 -*-
# 爬取百度贴吧楼主发送的文字内容,存储在本地文件中
# 导入模块
from urllib import request

# 定义查看楼主和页数
seelz = '?see_lz=' + '1'
pn = '&pn=' + '1'
# 定义基础url
base_url = 'https://tieba.baidu.com/p/4366865181'
# 定义参数url
arg_url = seelz + pn
# 完整url
url = base_url + arg_url
# 看一下打出来的网址有没有达到预期效果
print(url)

try:
    # 添加请求头,我发现贴吧不加头也能获取,不过最好还是加上
    head = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0'}
    # 创建对象
    req = request.Request(url, headers=head)
    # 打开网址
    response = request.urlopen(req)
    # 打印结果
    print(response.read().decode('utf-8'))
except request.URLError as e:
    if hasattr(e, 'code'):
        print(e.code)
    elif hasattr(e, 'reason'):
        print(e.reason)

输出结果如下: 可以看出成功获取了网页的内容。

2.3 正则表达式提取信息

接着就是提取对我们有用的信息了,可以分步操作,大事化小,小事化了,我们先提取标题,可以看一下网页的源代码,找到标题(浏览器为火狐): 通过上图操作我们可以找到标题部分的代码:

<h3 class="core_title_txt pull-left text-overflow   vip_red " title="【分析】关于“十年结局”的一些猜度" style="width: 396px">【分析】关于“十年结局”的一些猜度</h3>

下面开始写pattern pattern = re.compile('<h3 class="core_title_txt.?">(.?)</h3>')含义如下: 代码实现效果如下:

# -*- coding: utf-8 -*-
# 爬取百度贴吧楼主发送的文字内容,存储在本地文件中
# 导入模块
import re
from urllib import request

# 定义查看楼主和页数
seelz = '?see_lz=' + '1'
pn = '&pn=' + '1'
# 定义基础url
base_url = 'https://tieba.baidu.com/p/4366865181'
# 定义参数url
arg_url = seelz + pn
# 完整url
url = base_url + arg_url
# 看一下打出来的网址有没有达到预期效果
print(url)

try:
    # 添加请求头,我发现贴吧不加头也能获取,不过最好还是加上
    head = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0'}
    # 创建对象
    req = request.Request(url, headers=head)
    # 打开网址
    response = request.urlopen(req).read().decode('utf-8')
    # 定义过滤规则
    pattern = re.compile('<h3 class="core_title.*?">(.*?)</h3>')
    # 查询匹配结果
    result = re.search(pattern, response)
    # 判断是否存在结果,如果存在打印,如果不存在返回None
    if result:
        print(result.group(1).strip())
    else:
        print(None)
except request.URLError as e:
    if hasattr(e, 'code'):
        print(e.code)
    elif hasattr(e, 'reason'):
        print(e.reason)

执行结果如下: 好,基本满足我们的需求,那问题来了,如何获取本页所有的内容呢?我们继续,为了节省篇幅,代码只写修改的内容,其它部分和上面相同,如果参数不同,做替换即可

略
    # 打开网址
    response = request.urlopen(req).read().decode('utf-8')
    # 定义过滤规则
    pattern1 = re.compile('<h3 class="core_title.*?">(.*?)</h3>', re.S)
    # 过滤楼主说过的内容
    pattern2 = re.compile('<div id="post_content_.*?>(.*?)</div>', re.S)
    # 查询标题匹配结果
    result1 = re.search(pattern1, response)
    # 查询文章内容匹配结果
    items = re.findall(pattern2, response)
    # 通过for循环取得结果并打印
    for item in items:
        print(item)
略

结果实现了我们要获取内容个的想法,but,从图中我们也发现了,有很多<a>、<img>、<br>等标签干扰,那就继续,去掉干扰

# -*- coding: utf-8 -*-
# 爬取百度贴吧楼主发送的文字内容,存储在本地文件中
# 导入模块
import re
from urllib import request

# 定义查看楼主和页数
seelz = '?see_lz=' + '1'
pn = '&pn=' + '1'
# 定义基础url
base_url = 'https://tieba.baidu.com/p/4366865181'
# 定义参数url
arg_url = seelz + pn
# 完整url
url = base_url + arg_url
# 看一下打出来的网址有没有达到预期效果
print(url)

try:
    # 添加请求头,我发现贴吧不加头也能获取,不过最好还是加上
    head = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0'}
    # 创建对象
    req = request.Request(url, headers=head)
    # 打开网址
    response = request.urlopen(req).read().decode('utf-8')
    # 定义过滤规则
    pattern1 = re.compile('<h3 class="core_title.*?">(.*?)</h3>', re.S)
    pattern2 = re.compile('<div id="post_content_.*?>(.*?)</div>', re.S)
    # 查询标题匹配结果
    result1 = re.search(pattern1, response)
    # 查询内容匹配结果
    items = re.findall(pattern2, response)
    # 去除不必要的标签
    # 去除img标签
    removeImg = re.compile('<img.*?>')
    # 去除br标签
    replaceBR = re.compile('<br><br>|<br>')
    # 去除a标签
    removeA = re.compile('<a.*?>|</a>')
    # 去除其它除了上述标签的标签
    removeOtherTag = re.compile('<.*?>')
    for item in items:
        x = re.sub(replaceBR, '\n', item)
        x = re.sub(removeA, '', x)
        x = re.sub(removeImg, '', x)
        x = re.sub(removeOtherTag, '', x)
        print(x)

    # 判断是否存在结果,如果存在打印,如果不存在返回None
    if result1:
        print(result1.group(1).strip())
    else:
        print(None)

    if items:
        print(items)
    else:
        print(None)
except request.URLError as e:
    if hasattr(e, 'code'):
        print(e.code)
    elif hasattr(e, 'reason'):
        print(e.reason)

获取结果如下:

是不是看着有些意思了,感觉可以接受了,但是还有一个问题,就是代码看起来太乱了,不方便扩展,如果爬取别的网页呢?多页怎么办,好吧,那我们就用类的方式来实现,完整代码如下:

# -*- coding: utf-8 -*-


import re
from urllib import request


class InitTool:
    """
    定义初始化类,目的是去除获取代码内容中的各种无用标签
    """
    # 去除img标签
    removeimg = re.compile('<img.*?>')
    # 去除br标签
    replacebr = re.compile('<br><br>|<br>')
    # 去除a标签
    removea = re.compile('<a.*?>|</a>')
    # 去除其它除了上述标签的标签
    removeothertag = re.compile('<.*?>')

    # 定义类的方法,去除无用的标签
    def replace(self, x):
        x = re.sub(self.removeimg, '', x)
        x = re.sub(self.removea, '', x)
        x = re.sub(self.removeothertag, '', x)
        x = re.sub(self.replacebr, '\n', x)
        # 删除x前后多余的空格
        return x.strip()


class BdTb:
    """
    定义类,获取网页标题,内容
    """

    def __init__(self, baseurl, seelz):
        """
        传入基础地址,是否只看楼主等信息
        """
        # 基础地址
        self.baseurl = baseurl
        # 是否只看楼主
        self.seelz = '?see_lz=' + str(seelz)
        # 调用去除标签工具,去除无用标签,注意InitTool后面的括号不要丢
        self.inittool = InitTool()
        # 默认标题
        # self.defaultTitle = '百度贴吧'

    def getpage(self, pn):
        """
        传入页码,获取该页帖子代码,pn为页码
        """
        try:
            # 构建url
            url = self.baseurl + self.seelz + '&pn=' + str(pn)
            # 请求头信息
            head = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0'}
            # 发送请求
            req = request.Request(url, headers=head)
            # 打开网址获取内容
            response = request.urlopen(req)
            # 返回utf-8格式文本
            return response.read().decode('utf-8')
        except request.URLError as e:
            # 如果有reason,打印报错原因
            if hasattr(e, 'reason'):
                print('连接失败,错误原因:', e.reason)
                return None

    def gettitle(self, page):
        """
        获取帖子标题
        """
        # 定义获取标题正则,re.S代表.可以匹配任何内容,包括'\n'换行
        pattern = re.compile('<h3 class="core_title.*?">(.*?)</h3>', re.S)
        result = re.search(pattern, page)
        if result:
            # 如果存在返回标题
            return result.group(1).strip()
        else:
            return None

    def getpagenum(self, page):
        """
        获取帖子的总页数
        """
        # 定义获取总共页数的正则
        pattern = re.compile('<li class="l_reply_num.*?</span>.*?<span.*?>(.*?)</span>', re.S)
        result = re.search(pattern, page)
        if result:
            return result.group(1).strip()
        else:
            return None

    def getcontent(self, page):
        """
        获取帖子内容
        """
        # 定义获取帖子内容的正则
        pattern = re.compile('<div id="post_content_.*?>(.*?)</div>', re.S)
        # 获取所有内容
        items = re.findall(pattern, page)
        # 定义一个空列表,收集过滤后的内容
        contents = []
        for item in items:
            # 每一条内容前后加入一个空格
            content = "\n" + self.inittool.replace(item) + "\n"
            # 添加到列表中
            contents.append(content)
        return contents

    def start(self):
        """
        定义主方法,执行之后调用其它方法,将获取的标题和内容写入本地文件test.txt中
        """
        # 获取页面
        indexpage = self.getpage(1)
        # 获取总页数
        allpn = self.getpagenum(indexpage)
        # 获取标题
        title = self.gettitle(indexpage)
        if allpn is None:
            print('URL已失效,请重试')
            return
        print('总页数:%s' % allpn)
        # 写入标题
        with open('test.txt', 'w') as f:
            f.write(title)
        # 通过for循环,从1到总页数+1获取页码i,分别抓取每页的内容写入文件
        for i in range(1, int(allpn)+1):
            page = self.getpage(i)
            contents = self.getcontent(page)
            for content in contents:
                # 追加的方式写入文件
                with open('test.txt', 'a+') as f:
                    f.write(str(content))

if __name__ == '__main__':
    baseurl = 'https://tieba.baidu.com/p/4366865181'
    seelz = 1
    bdtb = BdTb(baseurl, seelz)
    bdtb.start()

结果如下:

本文内容参考:崔庆才的个人博客