爬取使用了Ajax技术的网页中的数据

问题分析

Ajax是一种用于创建快速动态网页的技术,通过在后台与服务器进行少量数据交换,使网页实现异步更新。这意味着在不重新加载整个网页的情况下,可以对网页的某部分进行更新。怎么判断一个网页是否使用了ajax技术呢?可以点击网页上任意更新网页内容的按钮,看一下网页的url(Uniform Resource Locator)是否发生变化,如果没有变化,应该就是使用了ajax技术。使用了这个技术的网页对应的html源代码里面没有包含我们需要的所有信息,这时单单使用requests库再用bs4库解析的套路就行不通了。解决方法有两种1

  1. 解析接口:既然网站的内容发生了变化,那就说明服务器传东西过来了,从而咱也一定向服务器发送了请求。我们只需要使用浏览器的检查工具,弄清楚向哪个url偷偷发送请求得到了数据就ok了,咱也去请求这个url。
  2. Selenium库(本文使用第一种方法)

本文以爬取https://www.cnss.com.cn/html/gkryjg/ 网站上的油价数据为例,说明如何使用解析接口的方式爬取使用了ajax技术的网页。
在这里插入图片描述

我们点击加载更多,可以发现网页偷偷向https://www.cnss.com.cn/api/content/list.jspx 这个url发送了请求,从负载中可以看到这个url的查询参数有三个。
在这里插入图片描述

在响应中我们可以看到这个url给网页返回了一些json数据构成的列表。列表中包含了网页更新内容对应的url。
在这里插入图片描述

多次尝试可以发现每次点击加载更多,网页都是悄悄向上述url偷偷发送请求,而查询参数部分只有first这一个参数发送变化,经过分析可以知道first参数实际上就是新一批内容首个内容的序号。到这里基本思路就出来了——首先,不断调整查询参数first向上述url发送请求,收集所有包含了某日油价数据网页的url。然后再依次对这些网页进行爬取,收集出这些网页中的数据。


编程实现

首先来设计一个url管理器,避免网页重复爬取2

# -*- coding: utf-8 -*-
"""
Created on Wed Feb 26 16:22:53 2025
urlmanager模块
@author: syaunsheng
"""

class Urlmanager(object):
    """url管理器"""
    def __init__(self):
        self.not_crawled_url_set = set() # 还没有被爬取的url的集合
        self.is_craled_url_set = set() # 已经被爬取的url的集合
        
    def has_url(self):
        """检查是否还有待爬取的url"""
        return len(self.not_crawled_url_set)
        
    def get_url(self):
        """从not_crawled_url_set返回一个url并放入is_craled_url_set"""
        if self.has_url():
            url = self.not_crawled_url_set.pop()
            self.is_craled_url_set.add(url)
            return url
        else:
            return None
    
    def add_url(self,url):
        if url not in self.not_crawled_url_set and url not in self.is_craled_url_set: 
            self.not_crawled_url_set.add(url)
    
    def add_urls(self,urls):
        for url in urls:
            self.add_url(url)

if __name__ == "__main__":
    urlmanager = Urlmanager()
    urlmanager.add_url("http://www.bilibili.com")
    print(urlmanager.not_crawled_url_set)
    print(urlmanager.get_url())
    print(urlmanager.not_crawled_url_set,urlmanager.is_craled_url_set)

然后收集所有待爬取网页的url,并依次爬取各个url。

# -*- coding: utf-8 -*-
"""
Created on Fri Feb 28 20:48:09 2025
@author: syaunsheng
"""
import requests
import pandas as pd
from io import StringIO 
from urlmanager import Urlmanager
from bs4 import BeautifulSoup

# 收集所有待爬取url
def get_all_necessary_urls(days=22):
    """默认获取近22天油价数据对应的所有网页的url"""
    all_necessary_urls = []
    counter = 0
    for first in range(11,days+1,11):
        r = requests.post(url1,headers={
            'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36 Edg/133.0.0.0',
            'referer':'https://www.cnss.com.cn/html/gkryjg/'
            },params={
                'first' : first,
                'count':  11,
                'channelIds': 63
                })
        if r.status_code == 200:
            counter += 11
            print('获取成功,url总数{}'.format(counter))
        else:
            print('获取失败')
            continue
        # url1返回的是json数据,对每一个json数据做单独处理
        for json_data in r.json():
            all_necessary_urls.append(base_url+json_data["url"])
    return all_necessary_urls

# 依次爬取每个url中的数据
def get_data_from_urls():
    big_df_lst = []
    while urlmanager.has_url():
        url = urlmanager.get_url()
        r = requests.get(url,headers={
            'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36 Edg/133.0.0.0',
            'referer':'https://www.cnss.com.cn/html/gkryjg/'
            })
        if r.status_code == 200:
            print('请求成功。剩余待访问url数={}'.format(urlmanager.has_url()))
        else:
            print('请求失败!剩余待访问url数={}'.format(urlmanager.has_url()))
            continue
        r.encoding = 'utf-8'
        #soup = BeautifulSoup(r.text,'html.parser')
        # 使用bs4解析获取每个url对应数据的日期,方便后续处理
        # date = soup.find('p',class_='article-title').get_text()[:10]
        date = url[37:45]
        # 使用pandas解析出表格数据
        s = StringIO();s.write(r.text);s.seek(0)
        df_lst = pd.read_html(s,header=0) 
        concatenated_df = pd.concat(df_lst,join='outer',ignore_index=True,axis=0)
        concatenated_df['date'] = date
        big_df_lst.append(concatenated_df)
    return pd.concat(big_df_lst,join='outer',ignore_index=True,axis=0)
    
if __name__ == '__main__':
    base_url = 'https://www.cnss.com.cn/' 
    url1 = "https://www.cnss.com.cn/api/content/list.jspx" 
    urlmanager = Urlmanager();urlmanager.add_urls(get_all_necessary_urls(days=330)) # 获取近330天的油价
    data = get_data_from_urls();data.to_excel('.\油价数据.xlsx')

实验结束,获得近330天的油价数据,并写入excel中。
在这里插入图片描述

附:前端基础知识

了解一些前端知识是python爬虫的基础。参考:

  1. html,css和javascript相关知识:https://www.bilibili.com/video/BV1BT4y1W7Aw
  2. http协议,浏览器开发者工具相关知识:https://www.bilibili.com/video/BV165411A7ef

HTML(Hyper Text Marked Language)、CSS(Cascading Style Sheets)、Javascript是构建现代网页的关键技术,它们之间有着紧密的关系。HTML是一种标记语言,主要用于描述网页的结构和内容;CSS用于设置网页的样式;JavaScript是一种脚本语言,用于增强网页的交互性和动态性。HTML和CSS相对简单,主要是把搞懂JavaScript搞懂。

  • HTML常用标签的含义以及常用CSS属性:https://zhuanlan.zhihu.com/p/9201482393
  • HTML的块标签、行内标签、行内块标签的含义和区别:https://blog.csdn.net/weixin_44706267/article/details/121022104
  • HTML中导入CSS的方式:https://www.runoob.com/w3cnote/html-import-css-method.html
  • CSS必须加分号的坑:https://www.zhihu.com/question/20283093
  • HTML,CSS,JS注释写法不同,避坑:https://blog.csdn.net/woshisangsang/article/details/120839199
  • CSS盒子模型概念,用于页面布局:https://blog.csdn.net/m0_46958731/article/details/114544954 推荐使用定位布局方式,更好控制。
  • JavaScript基本语法:https://blog.csdn.net/2301_80208072/article/details/143455858
  • JavaScript构成:https://blog.csdn.net/qq_52736131/article/details/123563321 ,其中dom这个概念非常重要,简单讲dom使得我们可以用js语言去操作html文档,获取html中的节点、修改节点内容、插入新节点、删除节点…使得网页发生动态变化。
  • JavaScript函数和触发事件的绑定:https://blog.csdn.net/wangxuanyang_zer/article/details/129626676

下面来实战,写一个网页,实现数据的增删查改。

<!DOCTYPE html>
<html lang='en'>

<head>
	<meta charset = 'utf-8'>
	<title>一个信息表格</title>
	<style>
		/*内嵌方式使用css*/
		table{
			width:100%;
			border-collapse:collapse; /*设置表格内元素无间隙*/
			margin-top:5px;
		}
		th,td{
			border:1px solid;
			text-align:center;
			padding:8px;
		}
		th{
			background-color:gray;
		}
		button{
			margin-right:5px;
		}
	</style>
</head>

<body>
	<h1 style="text-align:center">表格的增删改查</h1>
	<!--通过on前缀+事件类型的属性为新增数据这个按钮绑定监听函数add_data-->
	<button onclick='add_data()'>新增数据</button>
	<!--这里设置id的目的是便于js获取到这个节点-->
	<table id=0>
		<tr>
			<th>姓名</th>
			<th>电话</th>
			<th>操作</th>
		</tr>
	</table>
	<script src='./table.js'></script> <!--导入外部js代码-->
</body>

</html>
// table.js文件
// 给新增数据这个按钮创建监听函数
function add_data(){
	// 通过table节点的id属性获取到table节点
	var table_node = document.getElementById(0)
	// 新增数据的方法很简单,就是在html对应位置再插入一行三列
	var row_1d = table_node.rows.length // 获取要插入行对应的索引,然后使用table节点的insertRow方法插入row节点并返回
	var table_row = table_node.insertRow(row_1d)
	// 在再table_row节点中插入列节点对象并返回
	var table_col1 = table_row.insertCell(0)
	var table_col2 = table_row.insertCell(1)
	var table_col3 = table_row.insertCell(2)
	console.log(table_row)
	// 最后来设置table_col节点中间的文本
	table_col1.innerHTML = "未命名"
	table_col2.innerHTML = "无联系方式"
	// this属性相当于当前节点对象
	table_col3.innerHTML = "<button onclick='edit(this)'>编辑</button><button onclick='delete_data(this)'>删除</button>"
}

// 给删除这个按钮创建监听函数
function delete_data(button){
	// 参数传进来要删除的那行数据对应的button,方便定位,获取到它父节点的父节点
	var node_to_delete = button.parentNode.parentNode
	// 再找到node_to_delete的父节点——即整个表格,在在整个表格的基础上删掉node_to_delete节点即可
	node_to_delete.parentNode.removeChild(node_to_delete)
}

// 给编辑这个按钮创建监听函数
function edit(button){
	var row_to_edit = button.parentNode.parentNode
	var table_col1 = row_to_edit.childNodes[0]
	var table_col2 = row_to_edit.childNodes[1]
	var name = prompt('输入姓名')
	var phone = prompt('输入电话号')
	table_col1.innerHTML = name
	table_col2.innerHTML = phone
}

Hyper Text Transfer Protocol(http协议)相关知识参考:https://mp.weixin.qq.com/s/5zkMuUNMzugyfxNtg1VkIQ


  1. https://blog.csdn.net/m0_59162248/article/details/128048293 ↩︎

  2. https://www.bilibili.com/video/BV1CY411f7yh ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值