最近因为经济形势不好,黄金这样的硬通货价格持续走高,而且现在已经到了相当之高的程度。介于理财投资的低迷,黄金的长期投资说不定可以跑赢通胀。对于我们新手的投资,本着低买高卖的原则,总不会亏太多,这样我们就需要一个可以每天获取黄金价格的python脚本,每天爬取我们需要的黄金价格,然后大家可以根据需要添加发送邮件给自己等功能,做一个自动推送黄金价格的小工具。
先在网上随便找一个黄金价格的网站,比如下面的这个链接:
今日中国黄金最新价格查询(中国基础金价多少钱一克)-行情中心-金投网 (cngold.org)https://quote.cngold.org/gjs/swhj_zghj.html
如上图,我们可以看到,这个网站有每天的黄金基础价、零售价、回收价,下面的列表中有我们需要金价,点击上面的黄金基础价、零售价、回收价,下面的列表也会切换到相应的项目的价格上去,而我们的目标就是获取下面的列表中每天的金价了。
我们先试试edge浏览器的f12开发者调试模式,如下图:
切换到网络选项卡,按F5刷新网页,发现如下图:
那我们使用Fiddler直接抓包看看(你也可以使用Chrome浏览器的开发者模式抓包,这个网页不会报错,我想介绍给大家更通用的方法,所以使用了Fiddler)。打开我们的Fiddler Classic,随便选择一个左侧列表中嗅探出的网页项目,点击右上方Inspectors,并切换到JSON选项卡,如下图:
然后我们不停切换左侧列表嗅探出的网页,并观察右下角的JSON窗口的内容变化,当切换到
https://api.jijinhao.com/quoteCenter/historys.htm?codes=JO_52683&style=3&pageSize=30&_=1708922877928
以上链接时,如下图:
可以看到JSON窗口返回了我们需要的黄金单价,和网页中比较下,发现它是和网页的排除顺序相反,最新日期的数据是在最后的,而且通过这个网址传递的参数分析,它的参数如下:
codes=JO_52683
style=3
pageSize=30
time=1708922877928
显然其中的codes是切换黄金基础价、零售价、回收价的,一共有三种格式:
黄金基础价: JO_52683
零售价: JO_52684
回收价: JO_52685
style显示是一个类型,我们暂时不需要,pageSize是每次使用这个网址请求服务器返回数据时,服务器返回的数据条数数,pageSize=30就是返回30条数据,经过我用python的request发包测试,最少可以返回1条,最多返回500条数据。最后的time时间戳没有写明,但显然就是一个毫秒级时间戳(代表当前日期时间),实际测试时发现服务器并不验证这个时间戳,也就是重复传一个固定的时间戳也可以,当然我在我的代码中还是模拟了下当前时间戳。
# 将日期字符串转换为datetime对象
def date_to_time(date):date_format = '%Y-%m-%d'date_obj = datetime.strptime(date, date_format)# 获取UTC时间的时间戳(秒)timestamp_seconds = date_obj.timestamp()# 转换为毫秒timestamp_milliseconds = int(timestamp_seconds * 1000)return timestamp_milliseconds
下面是我用python构造了模拟发包获取服务器返回的json数据的函数:
def getPageData(pageSize,pagecount,data_style,header,time):res = requests.get("https://api.jijinhao.com/quoteCenter/historys.htm?codes=" + data_style + "&style=3&pageSize=" + str(pageSize) + "¤tPage=" + str(pagecount) + "&_=" + str(time),headers = header)# 获得json变量,里面记录着金价和记录日期quote_json_str = res.content.decode("utf-8")#print(quote_json_str)quote_json = json.loads(quote_json_str[4:].replace("quote_json = ",""))printout(pagecount,pageSize,quote_json,data_style)
我来解释下这个函数,第一句是使用requests库的get方法,通过传入我们构造的参数url网址和header发包头,来模拟发送给服务器,使之返回我们需要的json结果,其中pageSize(每页数量),pagecount(返回总页数),data_style(金价的类型),header(网页发送时的请求头),time(当前时间戳)都是我们构造函数的模拟参数,通过调用此函数时自定义传入即可。第二句是按照utf-8的格式转码我们获得的服务器返回值。因为服务器返回的是如下面格式字符串(以下的返回值我已做过格式化处理方便大家查看):
'var quote_json = {"flag": True,"style": 3,"data": {"JO_52683_digits": 2,"JO_52683_status": 100,"JO_52683_unit": "元/克","JO_52683_productName": "中国黄金基础金价","JO_52683": [{"q1": 480.0,"q2": 480.0,"q3": 480.0,"q4": 480.0,"q60": 1.0,"q62": 0.0,"time": 1708617600000}]}
}'
因此我们需把上面的字符串转换成我们可以操作的json格式,所以先用第三句的去掉这个字符串的参数名'var quote_json =,然后把这个字符串放入json.loads函数中载入成标准的json数据格式方便操作。我们观察下上面的序列构造,显然我们只需要JO_52683数组中的q1和time时间戳(此时间戳是金价数据对应的日期),经过一番构造后,有了如下函数:
def printout(pagecount,pageSize,json_data,data_style):#data_style表示返回金价类型:{'基础':'JO_52683','零售':'JO_52684','回收':'JO_52685'}count = 0for index in range(pageSize-1,-1,-1):#逆循环,因为返回每页的json都是从早的日期开始排列的,如果要使每页从比较晚的数据排列需要倒叙循环.item = json_data["data"][data_style][index]count += 1date = time_to_date(item["time"])if data_style == "JO_52683":price_style = "基础"elif data_style == "JO_52684":price_style = "零售"else:price_style = "回收"print(f"第{pagecount}页_{count}:日期:{date}, {price_style}金价:{item['q1']}元")
其中,一共4个参数,其他之前后已介绍过,json_data就是我们上一步获得的json格式数据,我们需要在这个函数中,解析出我们需要的数据。因为返回的json是逆序的日期排列,也就是最新的日期金价在最后面,我们我们需要逆循环读取出数据。
要逆向循环,即从高到低循环,你可以使用range
函数的三个参数:起始值、结束值和步长。当步长为负数时,可以实现逆向循环。如果你想要从pageSize - 1
开始到0
结束(包含0
),你可以这样做:
for index in range(pageSize - 1, -1, -1): # 你的代码
这里的range(pageSize - 1, -1, -1)
解释如下:
pageSize - 1
是循环的起始值,因为range
在Python中是包含起始值、不包含结束值的,所以要从pageSize - 1
开始。-1
是循环应该停止的值的前一个位置。由于不包括这个值本身,如果你想循环到0
,你需要写-1
作为结束值。-1
是每次循环中索引减少的量,即步长。
然后我们构造一个item变量,读取data键值中的"JO_52683"数组,也就是前面序列中的
"JO_52683": [{"q1": 480.0,"q2": 480.0,"q3": 480.0,"q4": 480.0,"q60": 1.0,"q62": 0.0,"time": 1708617600000}]
然后我们通过使用item["q1"]和item["time"]取出每个数组中的金价和日期时间戳就行了,然后通过下面的函数把时间戳转换成相应的日期格式:
def time_to_date(time):# 时间戳转换为日期timestamp = time / 1000 + 24 * 60 * 60 # 将毫秒转换为秒,因为是UTC时间需加24小时*60分钟*60秒return datetime.utcfromtimestamp(timestamp).strftime('%Y-%m-%d')
其中因为时间戳是UTC格式,是美国东时间,所以还要增加24小时*60分钟*60秒,因为是毫秒级需除以1000转换成秒,因此最终格式为timestamp = time / 1000 + 24 * 60 * 60,然后通过datetime.utcfromtimestamp函数的strftime转换成2024-02-26这样的格式即可。
最后我们只要把所有函数串联起来调用即可,如下面的完整代码:
import requests
from bs4 import BeautifulSoup as bs4
from datetime import datetime
import json
import timedef time_to_date(time):# 时间戳转换为日期timestamp = time / 1000 + 24 * 60 * 60 # 将毫秒转换为秒,因为是UTC时间需加24小时*60分钟*60秒return datetime.utcfromtimestamp(timestamp).strftime('%Y-%m-%d')def date_to_time(date):# 将日期字符串转换为datetime对象date_format = '%Y-%m-%d'date_obj = datetime.strptime(date, date_format)# 获取UTC时间的时间戳(秒)timestamp_seconds = date_obj.timestamp()# 转换为毫秒timestamp_milliseconds = int(timestamp_seconds * 1000)return timestamp_millisecondsdef printout(pagecount,pageSize,json_data,data_style):#data_style表示返回金价类型:{'基础':'JO_52683','零售':'JO_52684','回收':'JO_52685'}count = 0for index in range(pageSize-1,-1,-1):#逆循环,因为返回每页的json都是从早的日期开始排列的,如果要使每页从比较晚的数据排列需要倒叙循环.item = json_data["data"][data_style][index]count += 1date = time_to_date(item["time"])if data_style == "JO_52683":price_style = "基础"elif data_style == "JO_52684":price_style = "零售"else:price_style = "回收"print(f"第{pagecount}页_{count}:日期:{date}, {price_style}金价:{item['q1']}元")def getPageData(pageSize,pagecount,data_style,header,time):res = requests.get("https://api.jijinhao.com/quoteCenter/historys.htm?codes=" + data_style + "&style=3&pageSize=" + str(pageSize) + "¤tPage=" + str(pagecount) + "&_=" + str(time),headers = header)# 获得json变量,里面记录着金价和记录日期quote_json_str = res.content.decode("utf-8")#print(quote_json_str)quote_json = json.loads(quote_json_str[4:].replace("quote_json = ",""))printout(pagecount,pageSize,quote_json,data_style)header = {'Host': 'api.jijinhao.com','Connection': 'keep-alive','sec-ch-ua': '"Not A(Brand";v="99", "Microsoft Edge";v="121", "Chromium";v="121"','sec-ch-ua-mobile': '?0','User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0','sec-ch-ua-platform': '"Windows"','Accept': '*/*','Sec-Fetch-Site': 'cross-site','Sec-Fetch-Mode': 'no-cors','Sec-Fetch-Dest': 'script','Referer': 'https://quote.cngold.org/gjs/swhj_zghj.html','Accept-Encoding': 'gzip, deflate, br','Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6','Content-Type': 'text/html;charset=UTF-8','Connection': 'keep-alive',}def main():# 以下注释代码可以直接传入今天的日期date_str = datetime.now().strftime('%Y-%m-%d')# 把当前日期传入转换函数后,获得如"1708656891526"这样的毫秒时间戳time = date_to_time(date_str)#print(time)#设置获得的金价数据条目数(可以设置1-500,但是因为分页数关系,可能最后的一页会报错,设置值越大爬取越快,如果要完整爬取请使用默认值10)pageSize = 10#data_style表示返回金价类型:{'基础':'JO_52683','零售':'JO_52684','回收':'JO_52685'}data_style = 'JO_52684'count = 0#爬取你需要的总页数pageMax= 313while (count <= pageMax):getPageData(pageSize,count,data_style,header,time)count += 1if __name__ == "__main__":main()
其中的pageMax是你需要爬取的页数,取的大一点不要紧,因为爬取不到数据会报错停下。请看如下的爬取结果:
以上是我的爬虫教程,给大家学习参考用,请不要把爬虫用于不良目的,爬取测试时请添加延时,不要增加服务器负担,谢谢观看!