前言

生而为人,我很抱歉

作为21世纪的新时代年轻人,我不看抖音,不刷快手,每日都沉浸在对知识的渴望与科研的向往中,一天不学习我就浑身难受

当然,我也会好奇,现在的年轻人都喜欢啥

于是乎,借着学习(fu xi)Python的理由,写了这个小程序,这是个利用Requests模块编写的网络爬虫( suan shi ba ?)。可以爬取任意百度贴吧的所有帖子。

程序示例
程序示例

在我年轻的那个时代里,贴吧是人手必备,那个时候,贴吧要比现在火的多,各种学校贴吧、专业交流贴吧都是热火滔天。在这其中,更加被社会各界网友封神的就是李毅吧了。

...

但是,我今天进贴吧看了看,好像李毅吧也要凉了....

时代在变啊...

程序

功能:爬取任意百度贴吧的所有帖子,获取帖子标题和链接,并保存到根目录下的Tieba.data中。

升级空间:多线程(注意多线程文件重复打开的保存混乱问题)

存在问题:在爬取了8万多条数据后会被百度服务器识别出爬虫程序,服务器拒绝访问。

requests.exceptions.ConnectionError: HTTPSConnectionPool(host='tieba.baidu.com', port=443): Max retries exceeded with url: /f?kw=%E6%9D%8E%E6%AF%85&ie=utf-8&pn=81700 (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x000002147A0D4128>: Failed to establish a new connection: [WinError 10060] 由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败。'))

源代码:

import requests  # 导入网络请求模块
from lxml import etree  # 导入xpath语法分析模块


# 创建一个贴吧爬虫类
class TiebaSpider(object):

    def __init__(self, tieba_name):
        # 构建请求url
        self.url = 'https://tieba.baidu.com/f?ie=utf-8&kw={}&fr=search'.format(
            tieba_name)
        # 构建请求头
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'
        }
        # 一个计数器
        self.counter = 0

    # 定义类主体运行函数
    def run(self):
        while True:
            # 发送request请求
            get_data = self.get_web_page()
            # 处理返回数据
            next_page_href = self.handle_data(get_data)

            if next_page_href == None:
                # 当返回下一页url为None时,说明贴吧爬取数据结束
                break
            else:
                self.url = next_page_href

    # 发送get请求
    def get_web_page(self):
        request = requests.get(self.url, headers=self.headers)
        # print(request.content.decode())
        return request.content

    # 处理返回数据
    def handle_data(self, get_data):
        # 对数据解码并去除注释
        result_data = get_data.decode().replace("<!--", "").replace("-->", "")
        # 创建html对象
        html = etree.HTML(result_data)
        # 编写xpath提取语句提取所有帖子跳转a链接
        a_list = html.xpath("//a[contains(@class,'j_th_tit')]")

        # 循环对a标签进行信息获取
        for item in a_list:
            temp_dict = {}
            try:
                temp_dict["title"] = item.xpath('text()')[0]
                temp_dict["href"] = "https://tieba.baidu.com" + \
                    item.xpath('@href')[0]
            except:
                print(f"{self.counter}有一条a链接未能获取标题或链接...")
            else:
                # 获取数据成功,计数器加一
                self.counter += 1
                print(f"## 第{self.counter}条数据写入成功!##", temp_dict)
                # 写入文件
                with open('Tieba.data', 'a+', encoding='utf-8') as f:
                    f.write(str(temp_dict))
                    f.write(',\n')

        try:
            next_page_href = 'https:' + \
                html.xpath("//a[contains(text(),'下一页')]/@href")[0]
        except:
            next_page_href = None  # 未能获取下一页链接(说明到了最后一页)
        finally:
            return next_page_href


if __name__ == "__main__":
    my_spider = TiebaSpider('李毅')
    my_spider.run()

学习笔记

  1. 利用Python的Requests模块所获得的网页源码会与在浏览器中获取的网页源码不同,因为浏览器会进行渲染,会调取JS文件;
  2. 某些网站(例如:百度贴吧)服务器返回的源码会将网页html注释起来,这样的话浏览器是可以正常识别的,但是Python的lxml模块在提取html元素的时候是不能识别这些的,因此对于源代码在进行lxml提取元素前,要使用:

    result_data = get_data.decode().replace("<!--", "").replace("-->", "")
来去掉注释符号。
服务器返回注释
服务器返回注释
如何在浏览器中查看服务器返回源码
如何在浏览器中查看服务器返回源码

后记

昨天写了个贴吧的爬虫,本想看看现在的年轻人都喜欢看啥,但是爬了八万多条数据才发现现在玩儿贴吧的都是老年人。。。

于是乎,今天又来了斗鱼看看,听说现在的年轻人都爱这个。

源代码:

from selenium import webdriver
import time


# 创建一个爬取斗鱼网站的类
class Douyu(object):
    def __init__(self):
        # url
        self.url = 'https://www.douyu.com/directory/all'
        # 构建浏览器对象
        self.brower = webdriver.Chrome()

    # 类或对象运行函数

    def run(self):
        self.brower.get(self.url)  # 发送请求
        while True:
            next_page_btn = self.search_data()

            if next_page_btn == None:
                break
            else:
                self.brower.execute_script(
                    'scrollTo(0,1000000)')  # 页面较长,需要滚动才能获取元素
                next_page_btn.click()

        self.brower.quit()

    # 获取页面数据

    def search_data(self):
        time.sleep(3)  # 防止由于网速问题导致页面渲染未完成导致提取数据失败
        # 获取所有房间li标签
        room_list = self.brower.find_elements_by_xpath(
            '//li[contains(@class,"layout-Cover-item")]')

        for room in room_list:
            temp_dict = {}
            temp_dict['title'] = room.find_element_by_xpath('.//h3').text
            temp_dict['type'] = room.find_element_by_xpath(
                ".//span[contains(@class,'DyListCover-zone')]").text
            temp_dict['owner'] = room.find_element_by_xpath('.//h2').text
            temp_dict['peopleNum'] = room.find_element_by_xpath(
                ".//span[contains(@class,'DyListCover-hot')]").text
            # 写入数据
            self.write_file(temp_dict)

        # 寻找“下一页元素”
        next_page_btn = None
        try:
            next_page_btn = self.brower.find_element_by_xpath(
                "//span[contains(text(),'下一页')]")
        except:
            print("获取下一页按钮失败...")
        else:
            return next_page_btn

    # 写入文件

    @staticmethod
    def write_file(content):
        with open('Douyu.data', 'a+', encoding='utf-8') as f:
            f.write(str(content))
            f.write('\n')


if __name__ == "__main__":
    douyu = Douyu()
    douyu.run()

笔记:

  1. 不同于昨天的百度贴吧,斗鱼的前端网页与后端交互采用Ajax交互,这也就是说,每次请求新页面,地址栏URL并不会改变,所以这里在采用Requests模块的话显然更麻烦;
  2. 虽然使用selenium中的webdriver模块效率大大降低,但是却免去了构建POST信息的麻烦,慢就慢点吧,谁让自己太菜了。