192 lines
8.3 KiB
Python
Executable File
192 lines
8.3 KiB
Python
Executable File
import requests
|
|
import time
|
|
from datetime import datetime
|
|
import random
|
|
from fake_useragent import UserAgent
|
|
import logging
|
|
import write_to_excel as wte
|
|
import utils as Utils
|
|
|
|
# === 获取筛选条件 ===
|
|
KEYWORD =Utils.KEYWORD #关键词筛选
|
|
START_DATE =Utils.START_DATE
|
|
END_DATE =Utils.END_DATE
|
|
|
|
#获取公告来源链接
|
|
SOURCE_URL = Utils.SOURCE_URL
|
|
|
|
# === 反扒配置 ===
|
|
|
|
#获取代理列表
|
|
PROXIES = Utils.PROXIES
|
|
USER_AGENTS = Utils.USER_AGENTS
|
|
|
|
MAX_RETRIES = 3 # 最大重连次数
|
|
DELAY_MIN, DELAY_MAX = 1, 3 # 随机延迟范围(秒)
|
|
|
|
# === 日志配置 ===
|
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
|
|
|
|
def get_random_user_agent():
|
|
"""随机选择 User-Agent"""
|
|
return random.choice(USER_AGENTS)
|
|
|
|
def get_random_proxy():
|
|
"""随机选择代理"""
|
|
if not PROXIES:
|
|
return None
|
|
proxy = random.choice(PROXIES)
|
|
return {"http": proxy, "https": proxy}
|
|
|
|
def check_sensitive_words(user_agent,payload,category_code,childrencode):
|
|
"""敏感词检查请求"""
|
|
# === 接口地址 ===
|
|
url = "https://zfcg.gxzf.gov.cn/portal/sensitiveWords/check"
|
|
headers = {
|
|
"User-Agent": user_agent,
|
|
"Content-Type": "application/json;charset=UTF-8",
|
|
"Origin": "https://zfcg.gxzf.gov.cn",
|
|
"Referer": f"https://zfcg.gxzf.gov.cn/site/category?parentId={category_code}&childrenCode={childrencode}",
|
|
"Cookie": "_zcy_log_client_uuid=71e283e0-23d2-11f0-844a-eb67dfa7ab64"
|
|
}
|
|
response = requests.post(url, json=payload, headers=headers)
|
|
# print("这是敏感词检查请求返回的响应预览",response.json())
|
|
return response.json()
|
|
|
|
def get_announcements(category_code, childrencode,page_no=1, session=None):
|
|
"""请求接口获取公告列表函数(带反扒机制)"""
|
|
if session is None:
|
|
session = requests.Session()
|
|
payload = {
|
|
# 接口的请求参数
|
|
"keyword": KEYWORD, #关键词筛选,此参数即为搜索框中的关键词输入值
|
|
"publishDateBegin": START_DATE, #最早日期筛选
|
|
"publishDateEnd": END_DATE, #最晚日期筛选
|
|
"pageNo": page_no, #页码数
|
|
"pageSize": 15, #页容量
|
|
"categoryCode": category_code, # 该参数指定当前查找的公告栏目
|
|
"_t": int(time.time() * 1000) # 动态时间戳
|
|
}
|
|
|
|
# 先执行敏感词检查
|
|
user_agent=get_random_user_agent()
|
|
check_response = check_sensitive_words(user_agent,payload, category_code, childrencode)
|
|
if not check_response.get("success", False):
|
|
logging.warning("敏感词检查失败")
|
|
return None
|
|
|
|
# 再执行公告数据请求
|
|
# === 接口地址 ===
|
|
api_url = "https://zfcg.gxzf.gov.cn/portal/category"
|
|
headers = {
|
|
"User-Agent": user_agent,
|
|
"Content-Type": "application/json;charset=UTF-8",
|
|
"Origin": "https://zfcg.gxzf.gov.cn",
|
|
"Referer": f"https://zfcg.gxzf.gov.cn/site/category?parentId={category_code}&childrenCode={childrencode}",
|
|
"Cookie": "_zcy_log_client_uuid=71e283e0-23d2-11f0-844a-eb67dfa7ab64" # 如果需要登录
|
|
}
|
|
#若添加了至少1条代理,则尝试使用代理
|
|
if PROXIES:
|
|
proxy = 'Default_value'
|
|
else:
|
|
proxy=None
|
|
for retry in range(MAX_RETRIES):
|
|
try:
|
|
# 尝试使用代理,失败则切换为无代理
|
|
if proxy :
|
|
proxy = get_random_proxy()
|
|
|
|
if proxy:
|
|
logging.info(f"使用代理: {proxy['http']}")
|
|
else:
|
|
logging.info("未使用代理")
|
|
|
|
response = session.post(
|
|
api_url,
|
|
json=payload,
|
|
headers=headers,
|
|
proxies=proxy,
|
|
timeout=10
|
|
)
|
|
if response.status_code == 200:
|
|
data = response.json()
|
|
if data.get("success",True):
|
|
return data
|
|
else:
|
|
logging.warning(f"接口返回失败: {data.get('error', '未知错误')}")
|
|
else:
|
|
logging.warning(f"请求失败,状态码: {response.status_code}")
|
|
except requests.exceptions.ProxyError as pe:
|
|
# 代理错误时记录并跳过,继续无代理请求
|
|
logging.error(f"代理错误: {pe}. 切换为无代理模式")
|
|
proxy = None # 下次请求不使用代理
|
|
except Exception as e:
|
|
logging.error(f"请求异常: {e}")
|
|
|
|
# 重试前等待
|
|
delay = random.uniform(DELAY_MIN, DELAY_MAX)
|
|
logging.info(f"第 {retry + 1}/{MAX_RETRIES} 次重试,等待 {delay:.1f} 秒...")
|
|
time.sleep(delay)
|
|
|
|
return None # 所有重试失败
|
|
|
|
def parse_data(data,category_code,source_name):
|
|
"""解析公告数据函数"""
|
|
results = []
|
|
for item in data["result"]["data"]["data"]:
|
|
results.append({
|
|
"标题": item["title"],
|
|
"发布时间": datetime.fromtimestamp(int(item["publishDate"]) / 1000).strftime("%Y-%m-%d"),
|
|
"发布单位": item["purchaseName"],
|
|
"内容链接": f"https://zfcg.gxzf.gov.cn/site/detail?parentId={category_code}&articleId={item['articleId']}",
|
|
"来源栏目":source_name
|
|
})
|
|
return results
|
|
|
|
#爬虫主函数
|
|
def web_crawler():
|
|
session = requests.Session()
|
|
# print("这是本地会话存储:",session)
|
|
# 初始化引用值为信息公告,先爬取信息公告栏目的公告数据
|
|
childrencode = "ZcyAnnouncement"
|
|
# 爬取代码主体,外循环为来源栏目的循环,即遍历需要爬取的所有来源栏目
|
|
for key in SOURCE_URL:
|
|
page = 1
|
|
all_results = []
|
|
# print("这是正在爬取的栏目", SOURCE_URL[key][1])
|
|
# print("这是正在爬取的栏目的目录码", str(key))
|
|
# 判断正确的引用值
|
|
# 内循环主体即为在符合筛选条件的公告列表中遍历所有公告,爬取每个公告需要的指定数据。
|
|
while True:
|
|
# 这是分割线
|
|
# logging.info("爬虫日志分割线————————————————————————————————————————————————爬虫日志分割线")
|
|
# logging.info("爬虫日志分割线————————————————————————————————————————————————爬虫日志分割线")
|
|
# logging.info("爬虫日志分割线————————————————————————————————————————————————爬虫日志分割线")
|
|
# logging.info(f"正在爬取第 {page} 页...")
|
|
response = get_announcements(str(key), childrencode, page,session)
|
|
if response["result"]["data"]["empty"] or not response["result"]["data"]["data"]:
|
|
break
|
|
# logging.info(f"请求后共返回 {response["result"]["data"]["total"]} 条公告")
|
|
current_data = parse_data(response, SOURCE_URL[key][0], SOURCE_URL[key][1])
|
|
if not current_data:
|
|
break
|
|
# logging.info(f"解析后共爬取 {len(current_data)} 条公告")
|
|
all_results.extend(current_data)
|
|
page += 1
|
|
# 随机延迟
|
|
delay = random.uniform(DELAY_MIN, DELAY_MAX)
|
|
time.sleep(delay)
|
|
|
|
# print(f"从 {SOURCE_URL[key][1]} 共爬取 {len(all_results)} 条公告")
|
|
# 调用excel读写函数,将公告数据筛选后保存到Excel中
|
|
wte.write_to_excel(all_results,SOURCE_URL[key][1])
|
|
# 这是分割线
|
|
print("公告栏目分割线————————————————————————————————————————————————公告栏目分割线")
|
|
print("公告栏目分割线————————————————————————————————————————————————公告栏目分割线")
|
|
print("公告栏目分割线————————————————————————————————————————————————公告栏目分割线")
|
|
|
|
|
|
|
|
|
|
|