Py学习  »  Python

Python:爬取动态网站

连享会 • 5 月前 • 157 次点击  

👇 连享会 · 推文导航 | www.lianxh.cn

🍓 课程推荐:2024 机器学习与因果推断专题
主讲老师:司继春 (上海对外经贸大学) ;张宏亮(浙江大学)
课程时间:2024 年 11 月 9-10 日 ;16-17日
课程咨询:王老师 18903405450(微信)

 课程特色 · 2024机器学习与因果推断

  • 懂原理、会应用。本次课程邀请了两位老师合作讲授,目的在于最大限度地实现理论与应用的有机结合。为期四天的课程,分成两个部分:第一部分讲解常用的机器学习算法和适用条件,以及文本分析和大语言模型;第二部分通过精讲 4-6 篇发表于 Top 期刊的论文,帮助大家理解各类机器学习算法的应用场景,以及它们与传统因果推断方法的巧妙结合。
  • 以 Top 期刊论文为范例。目前多数人的困惑是不清楚如何将传统因果推断方法与机器学习结合起来。事实上,即便是 MIT 和 Harvard 的大牛们也都在「摸着石头过河」。为此,通过论文精讲和复现来学习这部分内容或许是目前最有效的方式了。张宏亮老师此前在浙江大学按照这一模式教授了「因果推断和机器学习」课程,效果甚佳:学生们能够逐渐建立起研究设计的理念,并在构造识别策略时适当地嵌入机器学习方法。 


作者:王颖 (四川大学)
邮箱:wangyingchn@outlook.com


目录

  • 1. 动态网页特征

  • 2. 动态网页爬取的基本思路

  • 3. 实战案例

    • 3.1 分析网页结构

    • 3.2 请求接口数据

    • 3.3 解析网页数据

    • 3.4 储存爬取数据

    • 3.5 循环爬取数据

  • 4. 完整代码

  • 5. 相关推文



温馨提示: 文中链接在微信中无法生效。请点击底部「阅读原文」。或直接长按/扫描如下二维码,直达原文:

在之前的推文中,我们介绍了如何爬取一个简单的静态网站——「Python爬取静态网站:以历史天气为例」,但是在实际过程中,常常会遇到需要爬取动态网站数据的情况。在本文中,我们也将通过一个比较简单的案例,来介绍爬取动态网站数据的基本思路和步骤。

1. 动态网页特征

首先,简单回顾一下动态网页的特征:

  • 从源代码看,动态网页的数据不会出现在网页源代码中,而是被 “藏” 起来了;
  • 从网址特征看,请求新数据时 (如翻页),网址不会变化。

2. 动态网页爬取的基本思路

动态网页数据爬取通常有两种方法:

  • 分析数据接口,找到数据藏在哪,然后请求接口的数据;
  • 通过 Selenium 模拟浏览器点击的方式获取数据。

在本次介绍中,我们将通过获取接口的方式来爬取动态网页的数据。由于动态网页结构会更加复杂一些,我们主要的精力是在解析网页结构这一步。但是在找到了数据接口后 (知道数据 “藏” 在哪),数据的爬取也比较简单。具体来看,爬取动态网页数据主要可分为以下几步:

  • 分析网页结构,查找数据接口;
  • 构造请求头,请求接口数据;
  • 解析接口数据;
  • 储存数据。

同样的,如果涉及多页的数据,需要分析接口的变化规律:

  • 分析单页网页结构,查找数据接口;
  • 分析接口变化规律,构造接口参数;
  • 循环请求、获取并解析数据;
  • 储存数据。

3. 实战案例

接下来,我们以爬取 bilibili 视频评论为例,来具体介绍如何通过 Python 爬取动态网页的数据。主要内容包括:

  • 如何分析动态网页结构、查找数据接口;
  • 如何请求接口数据;
  • 如何解析 json 格式数据;
  • 如何把数据实时存入 csv 文件;
  • 如何循环爬取多页数据。

3.1 分析网页结构

在动态网页的数据爬取中,分析网页结构至关重要。因为我们需要找到数据 “藏” 在哪,否则不知道应该去哪请求数据。在本案例中,我们将爬取「bilibili」的数据,具体选择 bilibili 入站第一名的视频「【才浅】15天花20万元用500克黄金敲数万锤纯手工打造三星堆黄金面具」的评论数据。

按照惯例,我们先看一下网页源代码,由于信息太多了,可以直接使用搜索功能。在网页源代码页面,搜索评论内容,发现没有这个数据。

那怎么办呢?我们就要找一找这个数据到底藏在哪了。通常,查找动态网页的数据接口有以下几个步骤:

  • 在页面右键鼠标选择 检查
  • 在检查页面选择 Network
  • 在 Network 页面选择类型,数据接口一般藏在Fetch/XHR 或者 JS 中;
  • 找到数据接口。

问题又来了,动态网页里的数据很多,有时候就算筛选了类型,也还是有很多页面。要找到我们需要的数据,真的是大海捞针,那怎么办呢?有一个小技巧,就是利用搜索功能。在检查页面,搜索一下评论内容,立马就找到了数据藏在哪里!

3.2 请求接口数据

历经千辛万苦,终于找到了数据藏的位置,接下来就是要把数据获取下来。这里简单三个步骤就可以完成:

  • 查找接口的网址:分析接口网址的情况;
  • 确定请求头数据:通常请求动态网页数据需要比较完整的请求头,这时可以直接把 Request Headers 里面的内容直接复制;
  • 请求数据:在有了数据接口的位置后,先尝试是否能够成功获取数据,可以直接通过 requests 请求数据。

我们先来看看接口网址 (Request URL) 的情况,可以看到网址包括以下几个部分:

  • 主要结构:https://api.bilibili.com/x/v2/reply/main;
  • 网址参数:callback、jsonp、next、type、oid、mode、plat、_。

也就是说,这个数据接口由 1 个主结构和 8 个参数构成。关于参数需要注意两点,一是有没有这个参数会不会影响数据获取,二是这个参数的含义和变化规律。具体的确定方法只有不断尝试:

  • 通过增删参数看是否会影响请求的结果,或者请求结果有何变化;
  • 通过变化网页看接口地址的变化规律,比如翻页、评论排序等,看参数如何变化。

当然,有时候不知道含义也可以爬取数据,但是建议还是了解一下,一般不清楚含义但必需的参数保持默认值即可。在这里,我们省略一下不断尝试的结果。最终,发现了部分参数的基本含义:

  • callback:代表一个获取数据的查询动作和时间戳,不是必需;
  • jsonp:代表获取的数据格式,不是必需;
  • next:代表页码,翻页循环时需要;
  • type:含义暂时不明,但是必需,且不变,值为 1;
  • oid:代表视频的 av 号,如果需要爬取多个视频的评论,需要从这个参数入手;
  • mode:代表评论的排序方式,2 = 按时间倒序,3 = 按热度排序 (默认排序)。不是必需,默认值为 3;
  • plat:含义暂时不明,不是必需;
  • _:代表当前的 Unix 时间戳,不是必需。

接下来我们就可以复制请求头,构造请求参数,请求需要的数据。

# 导入模块
import requests
import time

# 网址
url = "https://api.bilibili.com/x/v2/reply/main"  # 接口网址的主要结构

# 请求头数据
headers = {
    'accept''*/*',
    'accept-encoding''gzip, deflate, br',
    'accept-language''zh-CN,zh;q=0.9,en;q=0.8',
    'referer''https://www.bilibili.com/video/BV16X4y1g7wT',
    'sec-ch-ua''" Not A;Brand";v="99", "Chromium";v="100", "Google Chrome";v="100"',
    'sec-ch-ua-mobile''?0',
    'sec-ch-ua-platform''Windows',
    'sec-fetch-dest''script',
    'sec-fetch-mode''no-cors',
    'sec-fetch-site''same-site',
    'user-agent''Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
                  '(KHTML, like Gecko) Chrome/100.0.4896.60 Safari/537.36'
    # 根据测试不需要 cookie 信息也可以获取数据
    # 需要 cookie 的话在自己的浏览器中复制,这里涉及隐私就不放 cookie 信息了
}

# 构造请求参数
params = {
    # 'callback': 'jQuery17201888299578386794_' + str(round(time.time() * 1000)),
    # 'jsonp': 'jsonp',
    'next'0,  # 页码
    'type'1,
    'oid'715024588,  # 视频av号
    # 'mode': 3,  # 评论排序方式
    # 'plat': 1,
    # '_': str(round(time.time() * 1000))  # 生成当前时间戳
}

# 通过get方法请求数据
response = requests.get(url, headers=headers, params=params)

查看返回结果, 代表请求数据成功。如果是 403 或 404 则说明请求不成功,可能需要检查电脑网络是否通畅、目标网址是否可以正常访问、headers 是否有正确设置等。

3.3 解析网页数据

请求成功后,我们再来看请求回来的数据是什么样的,如何根据获取自己需要的数据。回到数据接口中,我们可以看到数据是通过 json 格式存储的,而每条评论的数据在 data 下面的 replies 中。

因此,我们用 json 解析请求到的数据,并把需要的评论数据提取出来。

# 导入模块
import json
import time

response.encoding = 'utf-8'                  # 修改编码格式
data_json = json.loads(response.text)        # 通过 json 解析数据
comment_list = data_json['data']['replies']  # 获取 data 下面的 replies 列表

comments = []                       # 构建空列表保存每页的评论数据
for i in range(len(comment_list)):  # 循环获取每条评论的数据
    comment = {
        'id': comment_list[i]['rpid'],  # 评论id
        # 评论时间,由时间戳转换
        'time': time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(comment_list[i]['ctime'])),  
        'parent': comment_list[i]['parent'],  # 父评论id
        'like': comment_list[i]['like'],      # 点赞数
        'user_id': comment_list[i]['member']['mid'],      # 评论用户id
        'user_name': comment_list[i]['member']['uname'],  # 用户名
        'content': comment_list[i]['content']['message']  # 评论内容
        # 需要其他数据的可以再在 json 中查看并获取对应的名称
    }
    comments.append(comment)  # 每页的评论数据

3.4 储存爬取数据

接下来,把爬取到的数据存入 csv 文件。当然,还是建议爬取一页保存一页。同时,使用 utf-8 格式保存数据,因此打开数据文件时,也要使用同样的格式。

# 导入模块
import csv

# 保存数据的文件路径
save_path = 'bilibili.csv'

# 将数据写入 csv
 with open(save_path, 'a', newline='', encoding='utf-8'as fp:
     csv_header = ['id''time''parent''like''user_id''user_name''content']  # 设置表头,即列名
     csv_writer = csv.DictWriter(fp, csv_header)
     # 如果文件不存在,则写入表头;如果文件已经存在,则直接追加数据不再次写入表头
     if fp.tell() == 0:
         csv_writer.writeheader()    
     csv_writer.writerows(comments)  # 写入数据

3.5 循环爬取数据

终于成功获取了一页的数据,接下来就要循环获取更多数据了。这里也分为三个步骤:

  • 分析接口网址的变化规律 (通常是参数的变化);
  • 根据规律构造网址;
  • 循环获取数据。

由于前面已经详细分析过接口的参数变化,这里不再具体说明。通过分析,翻页变化的参数是 next,所以只要变化这个参数就可以进行翻页。另外,如果要爬取不同视频的评论,则要通过 av 号来循环,也就是 oid 参数。

4. 完整代码

# -*- coding: utf-8 -*-
# Author: W.Y.
# Email: wangyingchn@outlook.com
# Date: 2022/4/12

# 导入模块
import requests  # 请求数据
import  time      # 时间模块
import json      # json 模块,储存数据
import csv       # 保存数据

# 请求数据
def get_response(page):
    url = 'https://api.bilibili.com/x/v2/reply/main'  # 接口网址的主要结构
    # 请求头数据
    headers = {
        'accept''*/*',
        'accept-encoding''gzip, deflate, br',
        'accept-language''zh-CN,zh;q=0.9,en;q=0.8',
        'referer''https://www.bilibili.com/video/BV16X4y1g7wT',
        'sec-ch-ua''" Not A;Brand";v="99", "Chromium";v="100", "Google Chrome";v="100"',
        'sec-ch-ua-mobile''?0',
        'sec-ch-ua-platform''Windows',
        'sec-fetch-dest''script',
        'sec-fetch-mode''no-cors',
        'sec-fetch-site''same-site',
        'user-agent''Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
                      '(KHTML, like Gecko) Chrome/100.0.4896.60 Safari/537.36'
        # 根据测试不需要 cookie 信息也可以获取数据
        # 需要 cookie 的话在自己的浏览器中复制,这里涉及隐私就不放 cookie 信息了
    }
    # 构造请求参数
    params = {
        # 'callback': 'jQuery17201888299578386794_' + str(round(time.time() * 1000)),
        # 'jsonp': 'jsonp',
        'next': page,  # 页码
        'type'1,
        'oid'715024588,  # 视频av号
        'mode'3,  # 评论排序方式
        # 'plat': 1,
        # '_': str(round(time.time() * 1000))  # 生成当前时间戳
    }
    # 通过get方法请求数据
    response = requests.get(url, headers=headers, params=params)
    return response

# 解析数据
def parse_data(response):
    response.encoding = 'utf-8'                  # 修改编码格式
    data_json = json.loads(response.text)        # 通过 json 解析数据
    comment_list = data_json['data']['replies']  # 获取 data 下面的 replies 列表
    comments = []                       # 构建空列表保存每页的评论数据
    for i in range(len(comment_list)):  # 循环获取每条评论的数据
        comment = {
            'id': comment_list[i]['rpid'],   # 评论id
            'time': time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(comment_list[i]['ctime'])),  
            # 评论时间,由时间戳转换
            'parent': comment_list[i]['parent'],  # 父评论id
            'like': comment_list[i]['like'],      # 点赞数
            'user_id': comment_list[i]['member']['mid'],      # 评论用户id
            'user_name': comment_list[i]['member']['uname'],  # 用户名
            'content': comment_list[i]['content']['message']  # 评论内容
            # 需要其他数据的可以再在 json 中查看并获取对应的名称
        }
        comments.append(comment)  # 每页的评论数据
    return comments

# 保存数据
def save_data(comments, save_path):
    with open(save_path, 'a', newline='', encoding='utf-8'as fp:
        # 设置表头,即列名
        csv_header = ['id''time''parent''like''user_id''user_name''content']  
        csv_writer = csv.DictWriter(fp, csv_header)
        # 如果文件不存在,则写入表头;如果文件已经存在,则直接追加数据不再次写入表头
        if fp.tell() == 0:
            csv_writer.writeheader()  
        csv_writer.writerows(comments)  # 写入数据

# 定义爬取函数
def crawler(page, save_path):
    time.sleep(2)  # 暂停 2 秒,避免请求过于频繁
    response = get_response(page)    # 请求数据
    comments = parse_data(response)  # 解析数据
    save_data(comments, save_path)   # 储存数据
    print(f'成功爬取第{page+1}页')

if __name__ == '__main__':
    save_file = 'bilibili.csv'  # 保存路径
    total_counts = 1000         # 爬取 1000 条评论
    # 如果要爬取所有评论,可以改成全部评论数。
    # 如果要爬取多个视频的评论,可以通过下面的代码,爬取第一页的时候返回所有的评论数
    # total_counts = data_json['data']['cursor']['all_count']
    # 页码循环,每页有 20 条评论,所以通过总评论数计算页码
    for p in range(total_counts//20 + 1):  
        crawler(p, save_file)

5. 相关推文

Note:产生如下推文列表的 Stata 命令为:
lianxh python, m
安装最新版 lianxh 命令:
ssc install lianxh, replace

  • 专题:数据分享
    • Python+Stata:如何获取中国气象历史数据
  • 专题:文本分析-爬虫
    • Stata+Python:爬取创历史新高股票列表
    • Python:爬取东方财富股吧评论进行情感分析
    • Python爬虫: 《经济研究》研究热点和主题分析
  • 专题:Python-R-Matlab
    • Python爬取静态网站:以历史天气为例
    • Python:绘制动态地图-pyecharts
    • Python爬虫1:小白系列之requests和json
    • Python爬虫2:小白系列之requests和lxml
    • Python爬虫:爬取华尔街日报的全部历史文章并翻译
    • Python爬虫:从SEC-EDGAR爬取股东治理数据-Shareholder-Activism
    • Python:爬取巨潮网公告
    • 司继春:Python学习建议和资源
    • Stata交互:Python-与-Stata-对比
    • Python:拆分文件让百万级数据运行速度提高135倍
    • Python+Stata:批量制作个性化结业证书
    • Python+Wind:用 Pyautogui 轻松下载 Wind 数据
    • Python:爬取上市公司公告-Wind-CSMAR
    • Python: 如何优雅地管理微信数据库?
    • Python: 6 小时爬完上交所和深交所的年报问询函
    • Python: 使用正则表达式从文本中定位并提取想要的内容
    • Python: 批量爬取下载中国知网(CNKI) PDF论文

🍓 课程推荐:2024 机器学习与因果推断专题
主讲老师:司继春 (上海对外经贸大学) ;张宏亮(浙江大学)
课程时间:2024 年 11 月 9-10 日 ;16-17日
课程咨询:王老师 18903405450(微信)

尊敬的老师 / 亲爱的同学们:

连享会致力于不断优化和丰富课程内容,以确保每位学员都能获得最有价值的学习体验。为了更精准地满足您的学习需求,我们诚挚地邀请您参与到我们的课程规划中来。请您在下面的问卷中,分享您 感兴趣的学习主题或您希望深入了解的知识领域 。您的每一条建议都是我们宝贵的资源,将直接影响到我们课程的改进和创新。我们期待您的反馈,因为您的参与和支持是我们不断前进的动力。感谢您抽出宝贵时间,与我们共同塑造更加精彩的学习旅程!https://www.wjx.cn/vm/YgPfdsJ.aspx# 再次感谢大家宝贵的意见!

New! Stata 搜索神器:lianxh  和 songbl  GIF 动图介绍
搜: 推文、数据分享、期刊论文、重现代码 ……
👉 安装:
  . ssc install lianxh
  . ssc install songbl
👉  使用:
  . lianxh DID 倍分法
  . songbl all

🍏 关于我们

  • 连享会 ( www.lianxh.cn,推文列表) 由中山大学连玉君老师团队创办,定期分享实证分析经验。
  • 直通车: 👉【百度一下: 连享会】即可直达连享会主页。亦可进一步添加 「知乎」,「b 站」,「面板数据」,「公开课」 等关键词细化搜索。

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/175309
 
157 次点击