爬取全国业余无线电中继频道
文章信息
创建日期:2025年1月8日
前不久买了UV-K6手台,玩了一段时间,刷了机。但是设置中继很麻烦,虽然 https://k5.vicicode.cn/ 网站中也有分享出来的,但是如果自己不能掌握最新信息,总感觉不是很舒服。所以直接自己写一个爬虫,还可以设置自己想要的功能。
本来打算用GPT帮我,浪费了很多时间,结果并不能让我满意。真要写什么逻辑强的代码或者细节太多的代码,GTP只能作一个辅助角色。
import requests
from bs4 import BeautifulSoup
import re
from lxml import etree
import openpyxl
from openpyxl import Workbook
import os
# 获取省份信息
sf_url = "http://weixin.cqcqcq.cn/"
response = requests.get(sf_url)
html = response.text
soup = BeautifulSoup(html, 'html.parser')
province_dict = {}
# 查找所有省份及对应的id, id用于组装网页链接
links = soup.find_all('a', class_='item-content item-link')
for link in links:
province_name_tag = link.find('div', class_='item-title bold')
if province_name_tag:
# 获取省份名称(如:黑龙江),并去除所有空白字符
province_name = province_name_tag.text.strip().replace(' ', '').replace('\n', '').replace('\r', '').replace('/', '\t').replace('区', '区 \t')
# 获取链接,并提取链接末尾的城市ID
link_href = link.get('href').strip()
match = re.search(r'(\d{1,2})$', link_href)
province_id = match.group(1) if match else "未知ID" # 如果匹配成功则提取数字,否则返回"未知ID"
# 保存备用
province_dict[province_name] = province_id
# 输出省份列表,供选择
print("\n以下是可供选择的省份:\n")
for idx, province in enumerate(province_dict.keys(), start=1):
print(f" [{idx}]\t{province}\n")
# 询问选择省份
while True:
try:
choice = int(input("请输入省份对应的序号:"))
print("")
if 1 <= choice <= len(province_dict):
selected_province = list(province_dict.keys())[choice - 1]
print(f"你选择了:{selected_province}\n")
lb_url = "http://weixin.cqcqcq.cn/index.php?m=radio&c=index&a=index&province_id="+ f"{province_dict[selected_province]}"
print(f"爬取链接为:{lb_url}\n")
break
else:
print("输入的序号不在范围内,请重新输入。")
except ValueError:
print("无效输入,请输入一个有效的序号。")
# 选择省份后,开始获取该省中继列表和对应的网页链接
response = requests.get(lb_url)
if response.status_code == 200:
soup = BeautifulSoup(response.text, 'html.parser')
# 获取链接
zj_links = []
# 查找所有符合条件的 <a> 元素,获取中继对应的网页链接
for a_tag in soup.find_all('a', href=True, class_="item-content item-link"):
link = a_tag['href']
if link.startswith("/"): # 判断链接是否是相对路径
link = lb_url.rstrip("/") + link # 拼接为完整路径
zj_links.append(link)
items = soup.find_all('div', class_='item-title radio-title')
# 获取名称
zj_names = []
for item in items:
# 获取主标题文本并去除多余空白
title = item.contents[0].strip() if item.contents else ""
zj_names.append(title)
else:
print(f"无法访问 {lb_url},状态码:{response.status_code}")
zj_types = [] # 中继类型
zj_receives = [] # 接收频率
zj_transmits = [] # 发射频率
zj_CTCSSs = [] # 亚音
print("正在爬取终中继信息,爬取时间由中继数量决定……\n")
# 获取中继参数信息
for link in zj_links:
try:
# 发送请求获取网页内容
response = requests.get(link)
response.raise_for_status() # 检查请求是否成功
# 解析 HTML 内容
tree = etree.HTML(response.content)
# 提取指定的元素文本
zj_type = tree.xpath("/html/body/div/div[1]/div/div[1]/div[2]/ul/li[1]/div/div[1]")
if zj_type[0].text == "类 型":
zj_type = tree.xpath("/html/body/div/div[1]/div/div[1]/div[2]/ul/li[1]/div/div[2]/text()")
# 将提取的文本添加到列表中
clean_text = re.sub(r'\s+', '', zj_type[0]) # 使用正则去除空白字符
zj_types.append(clean_text)
# 提取指定的元素文本
jspl = tree.xpath("/html/body/div/div[1]/div/div[1]/div[2]/ul/li[2]/div/div[1]")
if jspl[0].text == "主(下行)频率":
jspl = tree.xpath("/html/body/div/div[1]/div/div[1]/div[2]/ul/li[2]/div/div[2]")
# 将提取的文本添加到列表中
clean_text = re.sub(r'\s+', '', jspl[0].text) # 使用正则去除空白字符
zj_receives.append(clean_text)
# 提取指定的元素文本
fspl = tree.xpath("/html/body/div/div[1]/div/div[1]/div[2]/ul/li[4]/div/div[1]")
if fspl[0].text == "发射(上行)频率":
fspl = tree.xpath("/html/body/div/div[1]/div/div[1]/div[2]/ul/li[4]/div/div[2]")
# 将提取的文本添加到列表中
clean_text = re.sub(r'\s+', '', fspl[0].text) # 使用正则去除空白字符
zj_transmits.append(clean_text)
# 提取指定的元素文本
yy = tree.xpath("/html/body/div/div[1]/div/div[1]/div[2]/ul/li[5]/div/div[1]")
if yy[0].text == "亚 音":
try:
yy = tree.xpath("/html/body/div/div[1]/div/div[1]/div[2]/ul/li[5]/div/div[2]")
# 将提取的文本添加到列表中
text = yy[0].text # 使用正则去除空白字符
pattern = r"\d+\.?\d*"
clean_text = re.findall(pattern, text)
if clean_text[0] == "0":
zj_CTCSSs.append("")
else:
zj_CTCSSs.append(clean_text[0])
except Exception as e:
zj_CTCSSs.append("")
else:
zj_CTCSSs.append("")
except requests.exceptions.RequestException as e:
print(f"链接: {link} -> 请求失败: {e}")
except Exception as e:
print(f"链接: {link} -> 处理失败: {e}")
# 用于调试,查看中继各项信息总数是否相同
print(f"名称:{len(zj_names)}\n")
print(f"接收:{len(zj_receives)}\n")
print(f"发射:{len(zj_transmits)}\n")
print(f"亚音:{len(zj_CTCSSs)}\n")
# print(f"{zj_names}\n")
# print(f"{zj_types}\n")
# print(f"{zj_receives}\n")
# print(f"{zj_transmits}\n")
# print(f"{zj_CTCSSs}\n")
print("请检查,以上数据相同则为有效\n")
# 将获取的中继信息保存到Channel.xlsx,使用Channel-模板.xlsx,但不修改模板
# 设置相关文件路径
script_dir = os.path.dirname(os.path.abspath(__file__))
template_file_path = os.path.join(script_dir, "Channel-模板.xlsx")
output_file_path = os.path.join(script_dir, "Channel.xlsx")
if not os.path.exists(template_file_path):
print(f"模板文件不存在:{template_file_path}")
input("保存信道失败,请检查……")
exit()
# 打开模板文件
workbook = openpyxl.load_workbook(template_file_path)
sheet = workbook.active
# 获取中继总数
data_length = len(zj_names)
# 将列表数据填入指定列
for i in range(data_length):
sheet[f'B{i+2}'] = zj_names[i]
sheet[f'D{i+2}'] = zj_receives[i]
sheet[f'E{i+2}'] = zj_transmits[i]
sheet[f'K{i+2}'] = zj_CTCSSs[i]
sheet[f'U{i+2}'] = zj_links[i]
if len(zj_CTCSSs[i]) > 0:
sheet[f'J{i+2}'] = "亚音频"
# 另存为Channel.xlsx
workbook.save(output_file_path)
print(f"中继频道成功保存至:{output_file_path}\n")
input("按回车键退出")
最后保存的xlsx文件用 https://k5.vicicode.cn/#/chirp/channel 导入就行了。