用户、话题、评论一网打尽,分享一个最强微博爬虫

2019-10-14 23:20:04 浏览数 (1)

实现的功能

微博向来是一个极好的吃瓜圣地,为了获取微博上行行色色的数据,微博相关的爬虫也是层出不穷,因为无论是运营者还是数据分析从业者都或多或少需要微博数据,我的许多朋友也不例外,经过断断续续的努力,我完成了一个可能是史上最强大的微博爬虫的编写。

该爬虫的功能主要分为三部分,第一个主打功能是爬取指定用户的所有微博(可以通过热键 Ctrl P 快速打开),这个用户可以按照昵称搜索,可以选择是否只抓取原创微博,如下图

爬取的微博内容保存在 csv 文件中,表头包括微博id、微博正文、图片 url、发布位置、发布时间、发布工具和点赞数、评论数、转发数,其中图片 url 里面是所有微博图片的 url 以英文逗号间隔拼接起来的。

第二个主打功能是爬取指定话题下的所有微博(可通过热键 Ctrl B快速打开),如下图

爬取话题微博保存的 csv 格式大致和爬取用户微博格式类似。

第三个主打功能就是爬取根据微博 id 爬取该微博下的所有评论详情,比如微博 id 为 IaYZIu0Ko 的所有评论为:

除了爬虫的业务逻辑,大家也可以在上面看到,有较为友好的操作界面,方便大家操作。

技术路线

代码共计 1000 余行,不关心技术的同学可以跳过此部分

爬虫部分主要是 通过 Chrome 分析微博页面上的接口,获取接口参数,使用 requests 库模拟请求,需要带上 cookies ,我这个爬虫的大头其实是解析部分,我主要用了 lxml 库,需要解析的东西非常多,差不多 csv 中的每一个字段都需要单独的代码块来解析。

爬虫实现的三个功能:按用户爬取、按话题爬取、爬取微博所有评论,我分别用了三个类来实现,WeiboUserScrapy、WeiboTopicScrapy、WeiboCommentScrapy,三个类都有一些可以复用的函数,但是为了减少类之间的耦合,以及方便打包,我没有复用,这样单独一个类拿出来也能跑,减少了依赖。

再主要是界面模块的编写,我之前一直用 wxPython 编写界面,后来深入学习了 pyqt5 这个库,所以这个爬虫的界面是用 pyqt5 来写的,这里主要用到了 ListView model-view 模型、自定义信号及槽函数和一些常见组件的使用。

爬虫比较耗时,而界面又不允许阻塞,所以必须采用多线程技术,使用自定义信号作为爬虫类和界面类之间沟通的桥梁,比如爬虫开始、结束都会向界面类发出相应的信号完成界面的更新。

目前有个不完善的地方就是,后台任务除了进度框和打印,没有其他可视化查看的方法,而且各任务之间的调度只是简单的先到先服务,后续我会自定义调度器类,完成各种暂停、恢复、优先级处理等各种智能调度以及高级的可视化界面。

核心代码讲解

以 WeiboCommentScrapy 类为例,首先通过正则表达式获取评论总数,

代码语言:javascript复制
res = requests.get('https://weibo.cn/comment/{}'.format(self.wid),headers=self.headers,verify=False)
commentNum = re.findall("评论[.*?]",res.text)[0]
commentNum = int(commentNum[3:len(commentNum)-1])

然后根据评论总数分页

代码语言:javascript复制
pageNum = ceil(commentNum/10)

接着两层循环,外层遍历页数,内层遍历每一页的评论,最后对每一个评论做解析

代码语言:javascript复制
for page in range(pageNum):

    result = []

    res = requests.get('https://weibo.cn/comment/{}?page={}'.format(self.wid,page 1), headers=self.headers,verify=False)

    html = etree.HTML(res.text.encode('utf-8'))

    comments = html.xpath("/html/body/div[starts-with(@id,'C')]")

    print('第{}/{}页'.format(page 1,pageNum))

    for i in range(len(comments)):
        result.append(self.get_one_comment_struct(comments[i]))

    if page==0:
        self.write_to_csv(result,isHeader=True)
    else:
        self.write_to_csv(result,isHeader=False)
    # 休眠 1-5 秒,防止被封
    sleep(randint(1,5))

注意看内层循环,看上去每一页都是 10 条评论,实则不然,比如第一页有热门评论,会超过 10 条,最后一页可能没有 10 条,所以内层循环没有用 for i in range(10): 而是 for i in range(len(comments)):。内层循环还调用了一个函数 get_one_comment_struct():其作用是根据 xpath 得到的每一条 comment 元素解析得到我们想要的数据,其中又递归调用了几个自定义解析函数,比如解析得到的时间是诸如“xxx分钟前"、"刚刚",我们需要进一步做字符串处理得到具体时间戳。

由于本爬虫都是用 lxml 库采用 xpath 解析的,里面我遇到了许许多多的实用技巧,会在后续博客做详细展开。

0 人点赞