跳到主要内容

爬取全国业余无线电中继频道

前不久买了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 导入就行了。