爬虫请求响应以及提取数据

回顾:
网页给客户端响应数据, 有哪些写法(在爬虫入门之爬虫原理以及请求响应这篇博客咯嘛有提到)?
1.响应对象.text(获取网页数据的时候会用到)
2.响应对象.content(将图片, 音频或视频等数据存放到文件的时候会用到)

那这一篇文章, 介绍一个新的写法:
响应对象.json(), 这个方法在获取网页数据所对应的请求数据时会用到
什么情况下会使用
当再浏览器对应的响应中看到的数据结构有点像python中的字典或者列表(因为浏览器中不叫字典或者列
表,只是说数据格式类似)

我们通过代码的案例, 来讲解响应对象.json()的写法。

代码:

import requests
url = 'https://careers.tencent.com/tencentcareer/api/post/Query?
timestamp=1726832959000&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&
attrId=1&keyword=&pageIndex=1&pageSize=10&language=zh-cn&area=cn'
res = requests.get(url)
# print(type(res.text)) # 如果使用响应对象.text 那么得到的数据永远都是字符串类型
# print(type(res.json()))# 如果使用响应对象.text 那么得到的数据都是字典类型
data = res.json() # 使用data变量存储字典数据 字典更方便取值
# 结合循环编写解析的代码
count = 1 # 输出的序号
for i in data['Data']['Posts']:
    # 岗位名称
    RecruitPostName = i['RecruitPostName']
    # 所在城市
    LocationName = i['LocationName']
    # 岗位内容
    Responsibility = i['Responsibility']
    print(count,RecruitPostName,LocationName,Responsibility)
    count += 1

这个url怎么来的, 首先打开腾讯招聘

接着点击技术类那个框, 跳转到如下页面:

然后再请求中找到Query……, 这个就是我们需要爬取的数据请求。
每个网站, 需要爬取的数据请求都不一样, 这个需要自己找。
我们双击那个请求, 可以发现, 后台返回了数据。

所以url就是https://careers.tencent.com/tencentcareer/api/post/Query?
timestamp=1726832959000&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&
attrId=1&keyword=&pageIndex=1&pageSize=10&language=zh-cn&area=cn, 也就是请求。

说明:
data = res.json()这句话, 使用data变量存储字典数据, 因为字典更方便取值
我们可以发现请求里面, 有个Posts的列表(数组), 所以我们先要获取到那个列表再循环, 使用for i in data[‘Data’][‘Posts’]这句话进行循环, 因为Posts列表再Data里面, 所以要data[‘Data’][‘Posts’]这样写。然后在循环里面, 有三行代码, 获取岗位名称, RecruitPostName = i[‘RecruitPostName’], 获取所在城市, LocationName = i[‘LocationName’], 获取岗位内容, Responsibility = i[‘Responsibility’]。因为岗位名称、所在城市、岗位内容这三个东西都在Posts列表里面, 而i就是每一次遍历出来的列表里面的一条数据, 所以可以用i[‘字段名’]这么写。

伪装

url = 'https://movie.douban.com/j/chart/top_list?
type=10&interval_id=100%3A90&action=&start=0&limit=20'
# 响应的数据格式:[{},{}] 列表嵌套字典
res = requests.get(url)
data = res.json()
print(data) # 报错
# 得到的响应数据不是一个纯列表或者纯字典的时候 就会报如下错误
# requests.exceptions.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
# 改响应对象.text输出
print(res.text) # 当前获取到的数据是空字符串 为什么浏览器可以正常得到数据,而爬虫得不到

结果:

为什么会报错?
原因: 对方识别了当前的客户端是爬虫, 所以不给数据
如何解决: 我们需要用到伪装
如何实现伪装: 在请求头当中带上请求头(从浏览器复制过来)

修改过后的代码:

# 使用字典保存请求头信息
url = 'https://movie.douban.com/j/chart/top_list?
type=10&interval_id=100%3A90&action=&start=0&limit=20'
# 响应的数据格式:[{},{}] 列表嵌套字典
headers = {
    'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like
    Gecko) Chrome/128.0.0.0 Safari/537.36'
}
# 携带请求头的方式:headers参数传参
res = requests.get(url,headers=headers)
data = res.json()
print(data)
print(text)

此时此刻, 我们就能够获取到数据啦!

我们看一个完整的一个案例, 爬取豆瓣电影的数据:

import requests

url = 'https://movie.douban.com/j/chart/top_list?type=10&interval_id=100%3A90&action=&start=0&limit=20'
# user-agent对应的是客户端的版本信息Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36
headers = {
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36"
}
response = requests.get(url, headers=headers)
# print(response.text)
# print(type(response.json()))
data = response.json()
count = 1
for i in data:
    # i = 第一次循环取出的是 列表种的第一个字典 对应的是一个电影信息
    # 电影名
    title = i['title']
    # 演员表
    actors = i['actors']
    # 评分
    score = i['score']
    # 上映时间
    release_date = i['release_date']
    print(count, "电影标题:", title, ", 演员列表:", actors, ", 评分", score, ", 上映时间", release_date)
    count += 1

运行结果:


由于控制台的数据太多, 无法完全展示。
我们可以看到有电影名, 演员列表, 电影的评分以及电影的上映时间。

基于分页的爬虫

分页爬虫, 是非常常用的爬虫手段, 因为非常多的网站, 它不只是只有当前的一页数据, 它可以有n页的数据, 而我们却不知道这个网站有多少页数据的时候(因为网站会更新, 页数会变化), 我们就可以用分页查询法。

我们还是爬取豆瓣电影的网站
在写代码之前, 我们需要研究一下url, 也就是请求。
https://movie.douban.com/j/chart/top_list?type=10&interval_id=100%3A90&action=&start=0&limit=20
这个代表从第一条数据开始, 查询20条数据
start = 0代表从第一条数据开始, limit = 20代表查询20条数据
https://movie.douban.com/j/chart/top_list?type=10&interval_id=100%3A90&action=&start=20&limit=20
https://movie.douban.com/j/chart/top_list?type=10&interval_id=100%3A90&action=&start=40&limit=20
https://movie.douban.com/j/chart/top_list?type=10&interval_id=100%3A90&action=&start=60&limit=20
https://movie.douban.com/j/chart/top_list?type=10&interval_id=100%3A90&action=&start=80&limit=20
……
通过观察, 我们可以发现只有start参数会变
那如何实现0 20 40 60 80这样多个url呢?
我们需要用到循环
可以用一个变量代表start的值
然后每一次循环, 变量值+20

代码:

import requests

# 如果循环次数不确定, 就应该写死循环
# 定义页码变量(就是url里面的start)
page = 0
count = 1
# 因为我们不知道什么时候数据才能爬完, 也就是说我们不知道什么时候结束, 所以我们这里会用死循环, 之后根据观察当没有数据的时候, 控制台会返回什么信息, 再做决定如何跳出循环。
while True:
    # url会变, 用刚才定义好的page参数代表url里面的start
    url = f'https://movie.douban.com/j/chart/top_list?type=10&interval_id=100%3A90&action=&start={page}&limit=20'
    # 请求头
    headers = {
        "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36"
    }
    response = requests.get(url, headers=headers)
    data = response.json()
    for i in data:
        # i = 第一次循环取出的是 列表种的第一个字典 对应的是一个电影信息
        # 电影名
        title = i['title']
        # 演员表
        actors = i['actors']
        # 评分
        score = i['score']
        # 上映时间
        release_date = i['release_date']
        print(count, "电影标题:", title, ", 演员列表:", actors, ", 评分", score, ", 上映时间", release_date)
        print(f"第{count}页数据已经全部提取完毕")
        count += 1
    page += 20
    # 无限循环什么时候结束? 数据没有了就应该结束
    if len(data) == 0:
        print(f"第{count}页数据已经全部提取完毕")
        break

结果:

这里要说明一下, if len(data) == 0这句话是判断响应的数据, 是否是空列表, 也就是说判断数据是否已经爬完了。当然, 每个网站, 判断是否结束爬虫的判断方法, 都是不同的, 这个得看实际情况来判断。

这里我们为什么要用if len(data) == 0这样的判断, 我们再把刚才的代码运行一遍的同时再注释小判断结束的语句, 再加上一个print(data)语句, 我们看一看结果会如何。

代码:

import requests

# 如果循环次数不确定, 就应该写死循环
# 定义页码变量(就是url里面的start)
page = 0
count = 1
# 因为我们不知道什么时候数据才能爬完, 也就是说我们不知道什么时候结束, 所以我们这里会用死循环, 之后根据观察当没有数据的时候, 控制台会返回什么信息, 再做决定如何跳出循环。
while True:
    # url会变, 用刚才定义好的page参数代表url里面的start
    url = f'https://movie.douban.com/j/chart/top_list?type=10&interval_id=100%3A90&action=&start={page}&limit=20'
    # 请求头
    headers = {
        "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36"
    }
    response = requests.get(url, headers=headers)
    data = response.json()
    for i in data:
        # i = 第一次循环取出的是 列表种的第一个字典 对应的是一个电影信息
        # 电影名
        title = i['title']
        # 演员表
        actors = i['actors']
        # 评分
        score = i['score']
        # 上映时间
        release_date = i['release_date']
        print(count, "电影标题:", title, ", 演员列表:", actors, ", 评分", score, ", 上映时间", release_date)
        print(f"第{count}页数据已经全部提取完毕")
        count += 1
    page += 20
    print(data)
    # 无限循环什么时候结束? 数据没有了就应该结束
    # if len(data) == 0:
    #     print(f"第{count}页数据已经全部提取完毕")
    #     break

结果:

我们可以看到, 在265条数据以后, 都返回了空列表, 这就意味着数据到265条就结束了, 而且空列表的判断, 是判断data的长度是否为0(data的数据是列表里面套着字典,也就是[{},{}]这样的格式), 所以判断方法是if len(data) == 0。

同样的, 我们再看一个案例, 这个案例的判断爬虫是否结束的方法和上面的不一样了。

腾讯招聘网站分页:
代码:

import requests

"""
pageIndex: 查看的数据是第几页数据
"""
page = 1
# 获取所有数据
while True:
    # 获取1到10页数据
    count = 1
    url = f"https://careers.tencent.com/tencentcareer/api/post/Query?timestamp=1726832959000&countryId=&cityId=&bgIds=&productId=&category=&parentCategory=&attrId=1&keyword=&pageIndex={page}&pageSize=10&language=zh-cn&area=cn"
    res = requests.get(url)
    # 打印每一条响应的数据, 目的是需要观察当响应不到数据的时候, 在控制台返回的是什么。
    print(res.text)
    data = res.json()
    # TypeError: 'NoneType' object is not iterable
    # {"Code":200,"Data":{"Count":0,"Posts":null}}
    # 根据数据响应的结果, 进行结束循环的判断
    if data["Data"]['Posts'] is None:
        break
    for i in data["Data"]['Posts']:
        # 岗位名称
        RecruitPostName = i['RecruitPostName']
        # 工作地点
        LocationName = i['LocationName']
        # 工作内容
        Responsibility = i['Responsibility']
        print(count, RecruitPostName, LocationName, Responsibility)
        count += 1
    print(f"第{page}页数据加载完毕")
    page += 1

结果:

我们可以发现, 利用分页爬虫可以在腾讯招聘网里面爬取246条数据。同时我们可以在控制台的最下方, 看到有{“Code”:200,“Data”:{“Count”:0,“Posts”:null}}, 我们看到了Posts是null, 那我们就知道, 判断爬虫是否结束, 需要怎么去判断它了, 那这里, 我们需要用到
if data[“Data”][‘Posts’] is None, 判断data[“Data”][‘Posts’]是否为空作为判断爬虫是否结束。

将爬虫到的数据保存

这里, 我们先将数据保存到文本里面, 至于如何将数据保存到数据库或者excel等操作, 这些我们之后再讲。

其实, 把数据保存到文本里面非常的简单, 思路也很简单, 我们可以创建一个字符串变量, 那个变量就是用于存储爬虫的数据的, 最后我们将数据全部写入文件中。我们就那腾讯招聘的代码为例子。

代码:

import requests

"""
pageIndex: 查看的数据是第几页数据
"""
page = 1  # 页码
count = 1  # 输出的数据序号
content = '' # 为了存储爬虫的数据所用的变量
# 获取所有数据
while True:
    # 获取1到10页数据
    url = f"https://careers.tencent.com/tencentcareer/api/post/Query?timestamp=1726832959000&countryId=&cityId=&bgIds=&productId=&category=&parentCategory=&attrId=1&keyword=&pageIndex={page}&pageSize=10&language=zh-cn&area=cn"
    res = requests.get(url)
    print(res.text)
    data = res.json()
    # TypeError: 'NoneType' object is not iterable
    # {"Code":200,"Data":{"Count":0,"Posts":null}}
    # 根据数据响应的结果, 进行结束循环的判断
    if data["Data"]['Posts'] is None:
        break
    for i in data["Data"]['Posts']:
        # 岗位名称
        RecruitPostName = i['RecruitPostName']
        # 工作地点
        LocationName = i['LocationName']
        # 工作内容
        Responsibility = i['Responsibility']
        print(count, RecruitPostName, LocationName, Responsibility)
        # 调用操作系统, 打开文件, 往文件中写入数据。
        # 循环多少次, 就打开文件多少次, 效率不高
        # with open("腾讯招聘.txt", "a", encoding="utf-8") as f:
        #     f.write(f"{count}, {RecruitPostName}, {LocationName}, {Responsibility} \n")
        content += f"{count}, {RecruitPostName}, {LocationName}, {Responsibility} \n"
    count += 1
    print(f"第{page}页数据加载完毕")
    page += 1

# 将爬虫爬取到的数据都写到文件里面
with open("腾讯招聘.txt", mode="a", encoding="utf-8") as f:
    f.write(content)

注意:存储数据里面有些细节需要注意一下, 在for循环中, 有一句content += f"{count}, {RecruitPostName}, {LocationName}, {Responsibility} \n", 这句话的content后面必须是+=,而不是=, 这里面我们需要追加数据, 如果是=的话, 那就变成赋值了, 用=会导致只保存最后一条数据, 在最后面\n是换行, 为了保存的数据美观一些而不是挤在一起。还有一点就是, with open(“腾讯招聘.txt”, mode=“a”, encoding=“utf-8”) as f这句话, open函数的mode的值为a, w虽然有写入的模式, 但是会覆盖, 而a代表追加的意思, 这里建议写a。

运行结果:

我们打开腾讯招聘.txt文件


我们发现, 我们成功将爬虫爬取到的数据存入腾讯招聘.txt的文本中了。

实战:
给你一个url, 在那个网站上面进行爬虫
url = https://quote.eastmoney.com/ztb/detail#type=ztgc
找到这个网站数据对应的url发起请求 获取响应数据(解析的字段不限定,随意获取)
注意!!请求参数有一个cb=callbackdataxx 不要携带

提示:
找到网页当中的请求:
1.打开网站(https://quote.eastmoney.com/ztb/detail#type=ztgc), 然后再打开开发者工具f12, 找到网络。

我们发现, 这里面的请求太多了, 根本不能一下子找出来哪个是请求文件, 我们可以用搜索框来搜索我们想要爬取的数据, 然后它就会显示出哪个请求里面会有这个数据。我们随便在网站上面找一个数据进行搜索。
比如我们搜索一个002016, 就是网站的表格当中的第13条数据的其中一个数据。


点击搜索按钮, 输入002016, 按回车, 我们可以发现下面有数据。
点击下面返回的数据, 在右边选择响应。

我们可以发现, 请求就在这个里面。
我们再双击打开这个请求。

我们可以发现返回的是一些后端的数据, 这些后端的数据, 正是我们需要的响应数据。但是我们在最左上面可以发现有个callbackdata7124215, ()里面才是我们想要的数据, 那怎么办呢?
这个很简单, 在题目当中, 就已经告诉我们怎么取操作啦。

所以原本我们的请求url是https://push2ex.eastmoney.com/getTopicZTPool?cb=callbackdata7124215&ut=7eea3edcaed734bea9cbfc24409ed989&dpt=wz.ztzt&Pageindex=0&pagesize=20&sort=fbt%3Aasc&date=20240924&=1727160924310
但是我们要把cb=callbackdataxxx这个参数去掉, 所以我们的请求最终的url是https://push2ex.eastmoney.com/getTopicZTPool?ut=7eea3edcaed734bea9cbfc24409ed989&dpt=wz.ztzt&Pageindex=0&pagesize=20&sort=fbt%3Aasc&date=20240924&
=1727160924310

先不要马上看答案, 尝试自己做一做哦。

参考答案:

import requests

page = 0
content = ""
while True:
    url = f'https://push2ex.eastmoney.com/getTopicZTPool?ut=7eea3edcaed734bea9cbfc24409ed989&dpt=wz.ztzt&Pageindex={page}&pagesize=20&sort=fbt%3Aasc&date=20240923&_=1727104055700'
    headers = {
        "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36"}
    res = requests.get(url, headers=headers)
    data = res.json()
    if len(data['data']['pool']) == 0:
        break
    for i in data['data']['pool']:
        # 代码
        code = i['c']
        # 名称
        n = i['n']
        # 涨跌幅
        zdp = i['zdp']
        zdp_float = str(round(zdp, 2)) + "%"
        # 最新价
        p = i['p']
        p_f = p / 1000
        p_number = round(p_f, 2)
        # 成交额
        amount = i['amount']
        str_amount = ""
        if amount >= 10 ** 8:
            amount /= 10 ** 8
            string = str(round(amount, 2)) + "亿"
            str_amount += string
        else:
            amount /= 10 ** 4
            string = str(round(amount)) + "万"
            str_amount += string
        # 流通市值
        ltsz = i['ltsz']
        str_ltsz = ""
        ltsz /= 10 ** 8
        str_ltsz += str(round(ltsz, 2)) + "亿"
        # 总价值
        tshare = i['tshare']
        str_tshare = ""
        tshare /= 10 ** 8
        str_tshare += str(round(tshare, 2)) + "亿"
        # 换手率
        hs = i['hs']
        str_hs = str(round(hs, 2)) + "%"
        # 封板资金
        fund = i['fund']
        str_fund = ""
        if fund >= 10 ** 8:
            fund /= 10 ** 8
            string = str(round(fund, 2)) + "亿"
            str_fund += string
        else:
            fund /= 10 ** 4
            string = str(round(fund, 1)) + "万"
            str_fund += string
        # 首次封板时间
        fbt = i['fbt']
        str_fbt = ""
        if fbt >= 100000:
            string = str(fbt)
            str_fbt += string[0:2] + ":" + string[2:4] + ":" + string[4:6]
        else:
            string = str(fbt)
            str_fbt += string[0:1] + ":" + string[1:3] + ":" + string[3:5]
        # 最后封板时间
        lbt = i['lbt']
        str_lbt = ""
        if lbt >= 100000:
            string = str(lbt)
            str_lbt += string[0:2] + ":" + string[2:4] + ":" + string[4:6]
        else:
            string = str(lbt)
            str_lbt += string[0:1] + ":" + string[1:3] + ":" + string[3:5]
        # 炸板次数
        zbc = i['zbc']
        str_zbc = str(zbc) + "次"
        # 涨停统计
        zttj = str(i['zttj']['ct']) + "/" + str(i['zttj']['days'])
        # 连版数
        lbc = i['lbc']
        str_lbc = ""
        if lbc == 1:
            string = "首版"
            str_lbc += string
        else:
            str_lbc = str(lbc) + "连扳"
        # 所属行业
        hybk = i['hybk']
        content += "代码:" + str(code) + ",名称:" + n + ",涨跌幅:" + str(zdp_float) + ",最新价(千):" + str(p_number) + ",成交额:" + str_amount + ",流通市值:" + str_ltsz + ",总价值:" + str_tshare + ",换手率:" + str_hs + ",封板资金:" + str_fund + ",首次封板时间:" + str_fbt + ",最后封板时间:" + str_lbt + ",炸板次数:" + str_zbc + ",涨停统计:" + zttj + ",连版数:" + str_lbc + ",所属行业:" + hybk + "\n"
        print("代码:", code, ",名称:", n, ",涨跌幅:", zdp_float, ",最新价(千):", p_number, ",成交额:", str_amount,
              ",流通市值:", str_ltsz, ",总价值:", str_tshare, ",换手率:", str_hs, ",封板资金:", str_fund,
              ",首次封板时间:", str_fbt, ",最后封板时间:", str_lbt, ",炸板次数:", str_zbc, ",涨停统计:", zttj,
              ",连版数:", str_lbc, ",所属行业:", hybk)
        # print(i)
    page += 1

with open("涨停股池.txt", "a", encoding="utf-8") as f:
    f.write(content)

这里面的代码, 将爬虫爬取到的数据先格式化了, 数据格式化的效果和网站上表格的数据是一样的, 然后再把爬虫爬取到的数据存入txt文件里面去()。

你写出来了吗?如果写出来的话, 给自己一个掌声哦。

以上就是爬虫请求响应以及提取数据的所有内容了, 如果有哪里不懂的地方,可以把问题打在评论区, 欢迎大家在评论区交流!!!
如果我有写错的地方, 望大家指正, 也可以联系我, 让我们一起努力, 继续不断的进步.
学习是个漫长的过程, 需要我们不断的去学习并掌握消化知识点, 有不懂或概念模糊不理解的情况下,一定要赶紧的解决问题, 否则问题只会越来越多, 漏洞也就越老越大.
人生路漫漫, 白鹭常相伴!!!

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部