【import tkinter as tk
from tkinter import ttk, scrolledtext, messagebox, filedialog
import threading
from openpyxl import Workbook
from openpyxl.drawing.image import Image as ExcelImage
from PIL import Image
import requests
import base64
import json
import os
from datetime import datetime, timedelta
import time
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.chrome.options import Options
import cv2
import numpy as np
import easyocr
import io
import pyautogui
from selenium.webdriver.chrome.service import Service
import openpyxl as Excelopenpyxl
from openpyxl.utils import get_column_letter
class FOFASearchGUI:
def __init__(self, root):
self._stop_flag = False
self.root = root
self.root.title("FOFA搜索工具 - APK下载检测整合版")
self.root.geometry("1000x950")
root.geometry("+900+200")
# 变量
self.rules_file = "rules2.txt"
self.api_key = "723e2138c72d9320c5bb2e884cb43eed"
self.results = [] # 存储FOFA搜索结果
self.apk_results = {} # 存储每个URL对应的APK检测结果
self.setup_ui()
# 自动加载规则
self.auto_load_rules(0)
self.GuiZhi = None
# 自动点击配置
self.download_folder = self.setup_download_folder()
self.driver = None
self.ret_down_path = None
self.keywords = self.load_keywords_from_file()
# -------监控--------------------------
# 设置默认监控路径
self.active_downloads = {}
self.monitor_running = False
self.root.grid_rowconfigure(1, weight=1) # 进度区域可扩展
self.root.grid_columnconfigure(0, weight=1) # 主列可扩展
# 设置关闭事件处理
self.root.protocol("WM_DELETE_WINDOW", self.on_close)
# 初始化行计数器
self.row_counter = 0
# -------监控--------------------------
def setup_download_folder(self):
"""设置下载文件夹"""
download_folder = os.path.join(os.getcwd(), "downloads")
if not os.path.exists(download_folder):
os.makedirs(download_folder)
return download_folder
def get_past_date(self, days=3):
"""获取指定天数前的日期"""
past_date = datetime.now() - timedelta(days=days)
return past_date.strftime("%Y-%m-%d")
def setup_ui(self):
main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
# 设置左右列权重为7:3
main_frame.columnconfigure(0, weight=8) # 左侧区域占比70%
main_frame.columnconfigure(1, weight=2) # 右侧区域占比30%
# === 左侧区域 (规则和结果显示) ===
left_frame = ttk.Frame(main_frame)
left_frame.grid(row=0, column=0, rowspan=3, sticky=(tk.W, tk.E, tk.N, tk.S))
left_frame.columnconfigure(0, weight=1)
main_frame.rowconfigure(0, weight=1)
# 运行显示区域
rules_frame = ttk.LabelFrame(left_frame, text="运行结果", padding="5")
rules_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 5))
rules_frame.columnconfigure(0, weight=1)
rules_frame.rowconfigure(0, weight=1)
# self.rules_tree = ttk.Treeview(rules_frame, columns=(), height=8)
self.rules_tree = scrolledtext.ScrolledText(rules_frame, height=15, wrap=tk.WORD)
self.rules_tree.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# -------监控--------------------------
# 添加开始监控按钮
# =============== 中间进度区域 ===============
progress_frame = ttk.LabelFrame(self.root, text="下载进度", padding=10)
progress_frame.grid(row=1, column=0, sticky="nsew", padx=10, pady=5)
progress_frame.grid_rowconfigure(0, weight=1) # canvas行可扩展
progress_frame.grid_columnconfigure(0, weight=1) # canvas列可扩展
# 创建带滚动条的画布
self.canvas = tk.Canvas(progress_frame)
self.scrollbar = ttk.Scrollbar(
progress_frame,
orient="vertical",
command=self.canvas.yview
)
# 可滚动区域
self.scrollable_frame = ttk.Frame(self.canvas)
self.scrollable_frame.bind(
"<Configure>",
lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all"))
)
# 网格布局滚动区域
self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
self.canvas.configure(yscrollcommand=self.scrollbar.set)
# 布局画布和滚动条
self.canvas.grid(row=0, column=0, sticky="nsew")
self.scrollbar.grid(row=0, column=1, sticky="ns")
# =============== 底部按钮区域 ===============
bottom_frame = ttk.Frame(self.root, padding=10)
bottom_frame.grid(row=2, column=0, sticky="ew", padx=10, pady=5)
bottom_frame.grid_columnconfigure(0, weight=1) # 退出按钮区域居右
# 退出按钮
ttk.Button(
bottom_frame,
text="退出",
command=self.on_close,
width=10
).grid(row=0, column=1, sticky="e")
# -------监控--------------------------
scrollbar = ttk.Scrollbar(rules_frame, orient=tk.VERTICAL, command=self.rules_tree.yview)
scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
self.rules_tree.configure(yscrollcommand=scrollbar.set)
# 日志显示区域
result_frame = ttk.LabelFrame(left_frame, text="运行日志", padding="5")
result_frame.grid(row=2, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(5, 0))
result_frame.columnconfigure(0, weight=1)
result_frame.rowconfigure(0, weight=1)
self.result_text = scrolledtext.ScrolledText(result_frame, height=30, wrap=tk.WORD)
self.result_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 进度条
self.progress = ttk.Progressbar(left_frame, mode='determinate', maximum=100)
self.progress.grid(row=3, column=0, sticky=(tk.W, tk.E), pady=(5, 0))
# === 右侧区域 (其他组件) ===
# 创建右侧框架 - 修复未定义错误
right_frame = ttk.Frame(main_frame)
right_frame.grid(row=0, column=1, sticky=(tk.N, tk.S, tk.W, tk.E))
right_frame.columnconfigure(0, weight=1)
right_frame.rowconfigure(0, weight=1) # 添加此行确保Notebook填充空间
# 在right_frame中创建Notebook选项卡
notebook = ttk.Notebook(right_frame)
notebook.grid(row=0, column=0, sticky=(tk.N, tk.S, tk.W, tk.E))
# 创建选项卡1:搜索配置
tab_search = ttk.Frame(notebook)
notebook.add(tab_search, text="自定义配置搜索")
# 文件选择区域
file_frame = ttk.LabelFrame(tab_search, text="规则文件配置", padding="5")
file_frame.pack(fill="x", pady=(0, 10), padx=5)
ttk.Label(file_frame, text="规则文件:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5))
self.file_label = ttk.Label(file_frame, text=self.rules_file)
self.file_label.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(0, 5))
file_frame.columnconfigure(1, weight=1)
# 搜索配置区域
config_frame = ttk.LabelFrame(tab_search, text="搜索配置", padding="5")
config_frame.pack(fill="x", pady=(0, 10), padx=5)
ttk.Label(config_frame, text="返回数量:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5))
self.size_entry = ttk.Entry(config_frame, width=20)
self.size_entry.grid(row=0, column=1, sticky=tk.W, padx=(0, 5))
self.size_entry.insert(0, "1")
ttk.Label(config_frame, text="时间范围:").grid(row=1, column=0, sticky=tk.W, padx=(0, 5))
self.days_var = tk.StringVar(value="3")
days_spinbox = ttk.Spinbox(config_frame, from_=1, to=30, width=5, textvariable=self.days_var)
days_spinbox.grid(row=1, column=1, sticky=tk.W, padx=(0, 5))
ttk.Label(config_frame, text="天前").grid(row=1, column=2, sticky=tk.W)
# 搜索按钮区域
search_btn_frame = ttk.LabelFrame(tab_search, text="搜索相关按钮", padding="5")
search_btn_frame.pack(fill="x", pady=(0, 10), padx=5)
ttk.Button(search_btn_frame, text="开始搜索链接", command=self.perform_search).pack(fill="x", pady=2)
ttk.Button(search_btn_frame, text="清空结果", command=self.clear_results).pack(fill="x", pady=2)
ttk.Button(search_btn_frame, text="导出结果", command=self.export_results).pack(fill="x", pady=2)
ttk.Button(search_btn_frame, text="查看完整规则", command=self.show_full_rule).pack(fill="x", pady=2)
# APK检测按钮区域
apk_btn_frame = ttk.LabelFrame(tab_search, text="APK检测相关按钮", padding="5")
apk_btn_frame.pack(fill="x", pady=(0, 10), padx=5)
ttk.Button(apk_btn_frame, text="APK链接检测", command=self.start_apk_detection).pack(fill="x", pady=2)
ttk.Button(apk_btn_frame, text="存活链接检测", command=self.start_apk_detection).pack(fill="x", pady=2)
ttk.Button(apk_btn_frame, text="停止检测APK下载", command=self.stop_apk_detection).pack(fill="x", pady=2)
# APK下载相关按钮
apk_dou_frame = ttk.LabelFrame(tab_search, text="APK检测相关按钮", padding="5")
apk_dou_frame.pack(fill="x", pady=(0, 10), padx=5)
ttk.Button(apk_dou_frame, text="APK下载", command=self.process_single_url).pack(fill="x", pady=2)
ttk.Button(apk_dou_frame, text="APK重复删除", command=self.process_single_url).pack(fill="x", pady=2)
ttk.Button(apk_dou_frame, text="停止APK下载", command=self.stop_apk_detection).pack(fill="x", pady=2)
ttk.Button(apk_dou_frame, text="搜索保存网页图片", command=self.start_web_save).pack(fill="x", pady=2)
# 创建选项卡2:APK检测
tab_apk = ttk.Frame(notebook)
notebook.add(tab_apk, text="一键配置搜索")
# APK检测按钮区域
apk_btn_frame = ttk.LabelFrame(tab_apk, text="一键配置搜索", padding="5")
apk_btn_frame.pack(fill="x", pady=(0, 10), padx=5)
ttk.Button(apk_btn_frame, text="一键配置搜索", command=self.start_apk_detection).pack(fill="x", pady=2)
def log_message(self, message):
"""添加日志消息"""
# timestamp = datetime.datetime.now().strftime("%Y%m%d %H:%M:%S")
timestamp = datetime.now().strftime("%Y%m%d %H:%M:%S")
log_msg = f"[{timestamp}] {message}\n"
# self.result_text.config(state=tk.NORMAL)
self.result_text.insert(tk.END, log_msg + "\n")
self.result_text.see(tk.END)
# self.result_text.config(state=tk.DISABLED)
def result_message(self, message):
"""添加结果消息"""
self.rules_tree.insert(tk.END, message)
self.rules_tree.see(tk.END)
def start_web_save(self):
if not self.results:
messagebox.showwarning("警告", "请先执行FOFA搜索以获取目标链接")
return
threading.Thread(target=self.run_web_save, daemon=True).start()
def run_web_save(self):
# 配置 Chrome 浏览器选项
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')
# 创建 Excel 工作簿和工作表
wb = Workbook()
ws = wb.active
ws.append(['规则', '链接', '截图'])
for idx, result_row in enumerate(self.results):
# 假设 link 是第8列 (索引7),即 result_row[7]
# try:
url = result_row[0].strip()
guize = result_row[3].strip()
# 实例化浏览器
service = Service(executable_path=r'chromedriver.exe')
br = webdriver.Chrome(service=service,options=chrome_options)
# 打开链接
br.get(url)
# 截取网页图,以二进制形式获取
screenshot_binary = br.get_screenshot_as_png()
# 使用 io 模块将二进制数据转换为文件对象
img = ExcelImage(io.BytesIO(screenshot_binary))
# 将链接添加到 Excel 工作表中
ws.cell(row=idx + 2, column=1, value=guize)
ws.cell(row=idx + 2, column=2, value=url)
# 将图片插入到 Excel 工作表中
ws.add_image(img, f'C{idx + 2}')
self.log_message(f"保存{url}网页图片成功!" + "\n")
# 退出浏览器
br.quit()
# except Exception as e:
# self.log_message(f"处理链接 {result_row[7].strip()} 时出错: {e}" + "\n")
# 保存 Excel 文件
wb.save('web_and_screenshots.xlsx')
self.log_message(f"处理完毕!结果保存在web_and_screenshots.xlsx" + "\n")
def stop_apk_detection(self):
"""设置停止标志,等待线程自然退出"""
self._stop_flag = True
self.log_message("正在停止检测...")
def process_rule(self, rule):
rule = rule.strip()
if not rule:
return None, None
days = int(self.days_var.get())
past_date = self.get_past_date(days)
processed_rule = f'{rule} && after="{past_date}"'
base64_rule = base64.b64encode(processed_rule.encode('utf-8')).decode('utf-8')
return processed_rule, base64_rule
def auto_load_rules(self,zt):
"""加载规则文件"""
if not os.path.exists(self.rules_file):
print(f"规则文件不存在: {self.rules_file}")
return []
try:
with open(self.rules_file, 'r', encoding='utf-8') as f:
raw_rules = [line.strip() for line in f if line.strip()]
for line in raw_rules:
self.result_message(f"{line}\n\n")
processed_rules = []
for raw_rule in raw_rules:
processed_rule, base64_rule = self.process_rule(raw_rule)
if processed_rule and base64_rule:
processed_rules.append({
'raw': raw_rule,
'processed': processed_rule,
'base64': base64_rule
})
if zt ==0:
self.log_message(f"成功加载 {len(processed_rules)} 条规则")
return processed_rules
except Exception as e:
self.log_message(f"加载规则文件失败: {str(e)}")
return []
def perform_search(self, size=50):
self.progress['value'] = 0
self.results = []
try:
# 加载规则
rules = self.auto_load_rules(1)
if not rules:
self.log_message("没有可用的规则,程序退出")
return
# 对每条规则进行搜索
for i, rule_data in enumerate(rules):
try:
cha_rule = self.search_fofa(rule_data['base64'], rule_data['processed'])
self.results.extend(cha_rule)
self.log_message("规则:"+ "\n"f"{rule_data['processed']}" + "\n" + f"找到 {len(cha_rule)} 条结果:")
for sublist in cha_rule:
self.log_message(f"结果:{sublist[0]}")
except Exception as e:
self.log_message(f"规则 '{rule_data['processed']}' 搜索失败: {str(e)}")
self.log_message(f"搜索完成,共找到 {len(self.results)} 条结果")
except Exception as e:
self.root.after(0, messagebox.showerror, "错误", f"搜索过程中发生错误: {str(e)}")
finally:
self.progress.stop()
def search_fofa(self, base64_rule, rule_data):
url = f"https://fofa.info/api/v1/search/next"
params = {
'key': self.api_key,
'size': self.size_entry.get().strip(),
'fields': 'link,ip,lastupdatetime',
'qbase64': base64_rule
}
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.41 Safari/537.36 Edg/88.0.705.22',
'Accept-Encoding': 'gzip, deflate',
'Cache-Control': 'no-cache',
'Pragma': 'no-cache',
'Host': 'fofa.info',
'Accept': 'text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2',
'Connection': 'close'
}
response = requests.get(url, params=params, headers=headers, timeout=30)
response.raise_for_status()
data = response.json()
if data.get('error'):
raise Exception(f"API返回错误: {data.get('errmsg')}")
retu_dg = data.get('results', [])
for sub_list in retu_dg:
sub_list.append(rule_data)
return retu_dg
def update_results(self, rule_data, results):
self.log_message(f"规则 {rule_data['original']}: {rule_data['processed']}")
# self.log_message(f"找到 {len(results)} 条结果:\n{results['link']}")
self.log_message(f"找到 {len(results)} 条结果:")
for idx, result_row in enumerate(results):
self.log_message(result_row[0].strip())
def clear_results(self):
self.result_text.delete(1.0, tk.END)
self.results = []
self.apk_results = {}
self.log_message("结果已清空")
self.progress['value'] = 0
def export_results(self):
if not self.results:
messagebox.showwarning("警告", "没有结果可导出")
return
filename = filedialog.asksaveasfilename(
title="导出结果",
defaultextension=".json",
filetypes=[("JSON files", "*.json"), ("Text files", "*.txt"), ("All files", "*.*")]
)
if filename:
try:
with open(filename, 'w', encoding='utf-8') as f:
json.dump(self.results, f, ensure_ascii=False, indent=2)
messagebox.showinfo("成功", f"结果已导出到: {filename}")
except Exception as e:
messagebox.showerror("错误", f"导出失败: {str(e)}")
def show_full_rule(self):
selected = self.rules_tree.selection()
if not selected:
messagebox.showinfo("提示", "请先选择一个规则")
return
item = selected[0]
values = self.rules_tree.item(item)['values']
if values and len(values) >= 2:
messagebox.showinfo(
"完整规则信息",
f"处理后的规则:\n{values[0]}\nBase64编码:\n{values[1]}"
)
def start_apk_detection(self):
"""APK检测相关按钮"""
if not self.results:
messagebox.showwarning("警告", "请先执行FOFA搜索以获取目标链接")
return
threading.Thread(target=self.run_apk_detection, daemon=True).start()
def run_apk_detection(self):
"""APK检测相关按钮"""
self.log_message("APK检测完成未发现APK下载链接")
def load_keywords_from_file(self, filename="keywords.txt"):
"""从txt文件中读取关键字列表"""
keywords = []
try:
if os.path.exists(filename):
with open(filename, 'r', encoding='utf-8') as file:
for line in file:
keyword = line.strip()
if keyword:
keywords.append(keyword)
self.log_message(f"从 {filename} 中读取了 {len(keywords)} 个关键字: {', '.join(keywords)} "+ "\n")
else:
default_keywords = ['安卓下载', '立即下载','点击下载', 'Android download', 'Download','android download', 'download','下载', 'apk', '安装包', '安卓版',
'install', 'Android', 'android', 'download-apk', '客户端', '获取',
'down load', '安装']
with open(filename, 'w', encoding='utf-8') as file:
for keyword in default_keywords:
file.write(keyword + '\n')
keywords = default_keywords
self.log_message(f"创建了默认关键字文件 {filename},包含 {len(keywords)} 个关键字 "+ "\n")
except Exception as e:
self.log_message(f"读取关键字文件时出错: {e} "+ "\n")
keywords = ['安卓下载', '立即下载','点击下载', 'Android download', 'Download','android download', 'download','下载', 'apk', '安装包', '安卓版',
'install', 'Android', 'android', 'download-apk', '客户端', '获取',
'down load', '安装']
self.log_message(f"使用默认关键字 "+ "\n")
return keywords
def init_driver(self):
"""初始化浏览器驱动"""
self.ret_down_path = self.down_path()
if self.driver is not None:
return self.driver
options = Options()
options.add_argument('--ignore-certificate-errors')
options.add_argument('--ignore-ssl-errors')
options.add_argument('--disable-web-security')
options.add_argument('--allow-running-insecure-content')
options.add_argument('--disable-blink-features=AutomationControlled')
options.add_experimental_option("excludeSwitches", ["enable-automation", "enable-logging"])
options.add_argument("--window-size=1200,800")
custom_user_agent = "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36"
options.add_argument(f'--user-agent={custom_user_agent}')
prefs = {
"download.default_directory":self.ret_down_path,
"download.prompt_for_download": False,
"download.directory_upgrade": True,
"safebrowsing.enabled": True,
"profile.default_content_settings.popups": 0,
"profile.default_content_setting_values.notifications": 2,
}
options.add_experimental_option("prefs", prefs)
try:
# 初始化浏览器
service = Service(executable_path=r'chromedriver.exe')
self.driver = webdriver.Chrome(service=service,options=options)
# self.driver = webdriver.Chrome(options=options)
self.log_message("浏览器初始化成功 "+ "\n")
return self.driver
except Exception as e:
self.log_message(f"浏览器初始化失败: {str(e)} "+ "\n")
return None
def down_path(self):
"""创建新的带唯一时间文件夹"""
# 获取当前系统时间
curr_time = datetime.now()
time_str = curr_time.strftime('%m%d%H%M%S')
# 创建子文件夹
sub_folder_path = os.path.join(self.download_folder, time_str)
if not os.path.exists(sub_folder_path):
os.makedirs(sub_folder_path)
# 记录文件夹路径
return sub_folder_path
def gengxin_down_path(self):
"""更新下载文件夹"""
# 新的下载文件夹路径
new_download_folder = self.down_path()
options = Options()
# 更新下载文件夹
options.add_experimental_option("prefs", {
"download.default_directory": new_download_folder,
"download.prompt_for_download": False,
"download.directory_upgrade": True,
"safebrowsing.enabled": True
})
# 虽然更新了options,但需要重新创建一个新的会话来应用新的设置
# 这里可以通过执行一个简单的JavaScript来模拟重新加载浏览器设置
self.driver.execute_cdp_cmd('Page.setDownloadBehavior', {
'behavior': 'allow',
'downloadPath': new_download_folder
})
return new_download_folder
def close_driver(self):
"""关闭浏览器驱动"""
if self.driver:
try:
self.driver.quit()
self.driver = None
self.log_message(f"浏览器已关闭 "+ "\n")
except Exception as e:
self.log_message(f"关闭浏览器时出错: {str(e)} "+ "\n")
def find_keyword_locations(self):
"""使用EasyOCR识别页面中包含关键字的文字位置"""
if not self.driver:
return []
try:
# 等待页面完全加载
time.sleep(6)
# 获取页面截图
screenshot = self.driver.get_screenshot_as_png()
image = Image.open(io.BytesIO(screenshot))
open_cv_image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
# 初始化OCR阅读器
reader = easyocr.Reader(['ch_sim', 'en'])
results = reader.readtext(open_cv_image)
keyword_locations = []
key_word = []
# 查找包含关键字的文本
for (bbox, text, confidence) in results:
text = text.strip()
for keyword in self.keywords:
if keyword.lower() in text.lower():
# 计算边界框的中心点
top_left = bbox[0]
bottom_right = bbox[2]
center_x = int((top_left[0] + bottom_right[0]) / 2)
center_y = int((top_left[1] + bottom_right[1]) / 2)
keyword_locations.append({
'text': text,
'keyword': keyword,
'x': center_x,
'y': center_y,
'confidence': confidence
})
key_word.append(keyword)
self.log_message(f"找到关键字 '{keyword}' 在文字: '{text}',置信度: {confidence:.2f} "+ "\n")
break
# 按置信度排序
keyword_locations.sort(key=lambda x: x['confidence'], reverse=True)
return keyword_locations,screenshot,key_word
except Exception as e:
self.log_message(f"OCR识别失败: {e} "+ "\n")
return []
def get_window_position(self):
"""获取浏览器窗口在屏幕上的位置"""
if not self.driver:
return 0, 0
try:
window_rect = self.driver.get_window_rect()
return window_rect['x'], window_rect['y']
except:
return 0, 0
def click_all_locations_with_pyautogui(self, locations):
"""使用pyautogui点击所有找到的位置"""
if not locations:
self.log_message("没有找到可点击的位置 "+ "\n")
return False
window_x, window_y = self.get_window_position()
success_count = 0
self.log_message(f"准备点击 {len(locations)} 个位置 "+ "\n")
for i, location in enumerate(locations, 1):
try:
self.log_message(f"正在点击第 {i}/{len(locations)} 个位置: '{location['text']}' (关键字: '{location['keyword']}') "+ "\n")
# 计算在屏幕上的绝对坐标
screen_x = window_x + location['x'] + 10
screen_y = window_y + location['y'] + 80
# 移动鼠标到指定位置
pyautogui.moveTo(screen_x, screen_y, duration=0.3)
time.sleep(0.3)
# 模拟鼠标点击
pyautogui.click()
self.log_message(f"成功点击第 {i} 个位置: '{location['text']}' "+ "\n")
success_count += 1
# 点击后等待一段时间
time.sleep(2)
except Exception as e:
self.log_message(f"点击第 {i} 个位置时出错: {e} "+ "\n")
continue
self.log_message(f"成功点击了 {success_count}/{len(locations)} 个位置 "+ "\n")
return success_count > 0
def click_all_locations_with_javascript(self, locations):
"""使用JavaScript直接点击所有元素"""
if not locations:
return False
success_count = 0
for i, location in enumerate(locations, 1):
try:
self.log_message(f"正在使用JavaScript点击第 {i}/{len(locations)} 个位置: '{location['text']}' "+ "\n")
script = f"""
var element = document.elementFromPoint({location['x']}, {location['y']});
if (element) {{
element.click();
console.log('JavaScript点击成功');
return true;
}} else {{
console.log('未找到元素');
return false;
}}
"""
result = self.driver.execute_script(script)
if result:
self.log_message(f"JavaScript点击第 {i} 个位置成功 "+ "\n")
success_count += 1
else:
self.log_message(f"JavaScript点击第 {i} 个位置失败 "+ "\n")
# 点击后等待一段时间
time.sleep(2)
except Exception as e:
self.log_message(f"JavaScript点击第 {i} 个位置时出错: {e} "+ "\n")
continue
self.log_message(f"JavaScript成功点击了 {success_count}/{len(locations)} 个位置 "+ "\n")
return success_count > 0
def navigate_to_url(self, url):
"""导航到指定URL"""
if not self.driver:
return False
try:
self.log_message(f"正在打开网页: {url} "+ "\n")
self.driver.get(url)
# self.GuiZhi.set(url)
self.log_message("等待页面完全加载... "+ "\n")
# 使用显式等待等待页面加载完成
WebDriverWait(self.driver, 30).until(
lambda driver: driver.execute_script("return document.readyState "+ "\n") == "complete"
)
# 等待内容加载
time.sleep(5)
self.log_message("网页加载完成")
return True
except Exception as e:
self.log_message(f"导航到URL失败: {str(e)} "+ "\n")
return False
def get_save_excel(self, guize, url, gxshijian, tupian, key_word, gx_down_path):
"""下载结果保存FofaApkDownLog"""
try:
# 尝试打开已有的 Excel 文件
workbook = Excelopenpyxl.load_workbook('FofaApkDownLog.xlsx')
sheet = workbook.active
except FileNotFoundError:
# 如果文件不存在,创建一个新的工作簿和工作表
workbook = Excelopenpyxl.Workbook()
sheet = workbook.active
# 添加表头
headers = ['规则','链接','时间','截图','关键字','包名','大小','包地址']
for col_num, header in enumerate(headers, 1):
col_letter = get_column_letter(col_num)
sheet[f'{col_letter}1'] = header
try:
# 找到空白行
row_num = sheet.max_row + 1
# 插入数值
sheet.cell(row=row_num, column=1, value=guize)
sheet.cell(row=row_num, column=2, value=url)
sheet.cell(row=row_num, column=3, value=gxshijian)
# 将图片插入到 Excel 工作表中
img = ExcelImage(io.BytesIO(tupian))
# img = Image.open(io.BytesIO(tupian))
sheet2 = sheet.cell(row=row_num, column=4)
sheet.add_image(img, sheet2.coordinate)
sheet.cell(row=row_num, column=5, value=' '.join(map(str, key_word)))
sheet.cell(row=row_num, column=6, value='')
sheet.cell(row=row_num, column=7, value='')
sheet.cell(row=row_num, column=8, value=gx_down_path)
except Exception as e:
# 插入数值
sheet.cell(row=row_num, column=1, value="出错")
sheet.cell(row=row_num, column=2, value=url.strip())
sheet.cell(row=row_num, column=3, value="出错")
sheet.cell(row=row_num, column=4, value="出错")
sheet.cell(row=row_num, column=5, value="出错")
sheet.cell(row=row_num, column=6, value="出错")
self.log_message(f"保存{url.strip()}时出错: {e}" + "\n")
# 保存工作簿
workbook.save('FofaApkDownLog.xlsx')
self.log_message(f"下载结果保存在FofaApkDownLog.xlsx" + "\n\n")
# -------<监控--------------------------
def start_monitoring(self,folder_down_path):
"""开始监控线程"""
# 初始化行计数器
self.row_counter = 0
# 扫描并添加已有的apk文件
self.add_existing_apk_files(folder_down_path)
self.monitor_running = True
# 创建线程监控文件下载
self.monitor_thread = threading.Thread(target=self.monitor_folder, args=(folder_down_path,))
self.monitor_thread.daemon = True
self.monitor_thread.start()
def add_existing_apk_files(self,folder_down_path):
"""添加文件夹中已存在的APK文件到列表"""
if not os.path.exists(folder_down_path):
return
for filename in os.listdir(folder_down_path):
if filename.endswith('.apk'):
file_path = os.path.join(folder_down_path, filename)
try:
file_size = os.path.getsize(file_path)
self.add_completed_file(filename, file_size)
except OSError:
continue
def stop_monitoring(self):
"""停止监控线程"""
if self.monitor_running:
self.monitor_running = False
if hasattr(self, 'monitor_thread') and self.monitor_thread.is_alive():
self.monitor_thread.join(timeout=1.0)
def update_progress(self, filename, current_size, increment, is_new=False):
"""更新下载进度条"""
if filename not in self.active_downloads:
# 创建新的进度条组件(使用网格布局)
frame = ttk.Frame(self.scrollable_frame, padding=5)
# 新下载文件放在最上面
if is_new:
frame.grid(row=0, column=0, sticky="ew", pady=2)
# 将已有项目下移
for name, data in self.active_downloads.items():
current_row = data['frame'].grid_info()['row']
data['frame'].grid(row=current_row + 1)
else:
frame.grid(row=self.row_counter, column=0, sticky="ew", pady=2)
self.row_counter += 1
frame.grid_columnconfigure(1, weight=1) # 进度条区域可扩展
# 文件名标签
file_label = ttk.Label(frame, text=filename, width=30, anchor="w")
file_label.grid(row=0, column=0, padx=(0, 5), sticky="w")
# 进度条
progress = ttk.Progressbar(
frame,
orient="horizontal",
length=300,
mode="determinate",
maximum=(current_size + 1024 * 100) / 1024
)
progress.grid(row=0, column=1, sticky="ew", padx=5)
# 下载信息标签
size_label = ttk.Label(frame, text=f"{current_size / 1024:.2f} KB", width=15, anchor="e")
size_label.grid(row=0, column=2, padx=5)
# 速度标签
speed_label = ttk.Label(frame, text="0 KB/s", width=10, anchor="e")
speed_label.grid(row=0, column=3, padx=5)
# 存储组件引用
self.active_downloads[filename] = {
'frame': frame,
'progress': progress,
'size_label': size_label,
'speed_label': speed_label,
'last_size': current_size,
'last_time': time.time(),
'total_downloaded': 0,
'completed': False
}
else:
data = self.active_downloads[filename]
if data['completed']:
return
# 计算下载速度
current_time = time.time()
time_diff = current_time - data['last_time']
speed_kbs = (increment / 1024) / time_diff if time_diff > 0 else 0
# 更新UI组件
current_kb = current_size / 1024
data['progress']['value'] = current_kb
data['size_label'].config(text=f"{current_size / 1024:.2f} KB")
data['speed_label'].config(text=f"{speed_kbs:.2f} KB/s")
# 更新内部状态
data['last_size'] = current_size
data['last_time'] = current_time
data['total_downloaded'] += increment
def add_completed_file(self, filename, final_size):
"""添加已完成的文件到列表"""
# 创建新的进度条组件(使用网格布局)
frame = ttk.Frame(self.scrollable_frame, padding=5)
frame.grid(row=self.row_counter, column=0, sticky="ew", pady=2)
self.row_counter += 1
frame.grid_columnconfigure(1, weight=1) # 进度条区域可扩展
# 文件名标签
file_label = ttk.Label(frame, text=filename, width=30, anchor="w")
file_label.grid(row=0, column=0, padx=(0, 5), sticky="w")
# 进度条(设置为100%)
progress = ttk.Progressbar(
frame,
orient="horizontal",
length=300,
mode="determinate",
value=100,
maximum=100
)
progress.grid(row=0, column=1, sticky="ew", padx=5)
# 下载信息标签
size_label = ttk.Label(frame, text=f"{final_size / 1024:.2f} KB", width=15, anchor="e")
size_label.grid(row=0, column=2, padx=5)
# 速度标签(显示已完成)
speed_label = ttk.Label(frame, text="完成", width=10, anchor="e")
speed_label.grid(row=0, column=3, padx=5)
# 存储组件引用
self.active_downloads[filename] = {
'frame': frame,
'progress': progress,
'size_label': size_label,
'speed_label': speed_label,
'completed': True
}
def complete_download(self, filename, final_size,folder_down_path):
"""标记下载完成"""
if filename in self.active_downloads:
data = self.active_downloads[filename]
data['progress'].configure(value=100, maximum=100)
data['size_label'].config(text=f"{final_size / 1024:.2f} KB")
data['speed_label'].config(text="完成")
data['completed'] = True
def remove_progress(self, filename):
"""移除完成的下载进度条"""
if filename in self.active_downloads:
data = self.active_downloads[filename]
data['frame'].destroy()
del self.active_downloads[filename]
# 重新计算行号
self.row_counter = 0
for i, (name, item) in enumerate(self.active_downloads.items()):
item['frame'].grid(row=i, column=0, sticky="ew", pady=2)
self.row_counter = i + 1
def monitor_folder(self,folder_down_path):
"""监控文件夹的核心函数"""
while self.monitor_running:
try:
# 1. 检查APK文件
for filename in os.listdir(folder_down_path):
if filename.endswith('.apk'):
file_path = os.path.join(folder_down_path, filename)
file_size = os.path.getsize(file_path)
# 标记对应的下载任务完成
crdownload_name = filename.replace('.apk', '.crdownload')
if crdownload_name in self.active_downloads:
self.root.after(0, self.complete_download, crdownload_name, file_size,folder_down_path)
# 2. 扫描下载中的.crdownload文件
current_files = set(os.listdir(folder_down_path))
crdownload_files = [f for f in current_files if f.endswith('.crdownload')]
# 4. 处理下载文件
for filename in crdownload_files:
file_path = os.path.join(folder_down_path, filename)
try:
current_size = os.path.getsize(file_path)
last_size = self.active_downloads[filename][
'last_size'] if filename in self.active_downloads else 0
increment = current_size - last_size
if increment > 0 or filename not in self.active_downloads:
# 新下载文件放在最上面
is_new = filename not in self.active_downloads
self.root.after(0, self.update_progress, filename, current_size, increment, is_new)
except FileNotFoundError:
self.root.after(0, filename)
except Exception:
pass
time.sleep(1)
except Exception:
time.sleep(5)
def on_close(self):
"""关闭应用时停止监控线程"""
self.stop_monitoring()
self.root.destroy()
# -------监控>--------------------------
def process_single_url(self):
"""L:打开页面,识别并点击所有下载按钮"""
# 初始化浏览器
if not self.init_driver():
self.result_text.insert(tk.END,"浏览器初始化失败,程序退出 "+ "\n")
return
if not self.driver:
self.log_message("浏览器未初始化 "+ "\n")
return False
if not self.results:
messagebox.showwarning("警告", "请先执行FOFA搜索以获取目标链接 "+ "\n")
return
# 进度条
self.progress['value'] = 0
total = len(self.results)
if total == 0:
return
for idx, result_row in enumerate(self.results):
# 假设 link 是第8列 (索引7),即 result_row[7]
try:
url = result_row[0].strip()
guize = result_row[3].strip()
gxshijian = result_row[2].strip()
self.log_message(f"正在检测APK: {idx + 1}/{total} " + "\n")
# 更新下载文件夹
gx_down_path = self.gengxin_down_path()
self.log_message(f"更新了下载文件夹: {gx_down_path} " + "\n")
# 导航到目标URL
if not self.navigate_to_url(url):
return
self.log_message(f"正在使用EasyOCR识别页面中的关键字文字... "+ "\n")
keyword_locations,tupian,key_word= self.find_keyword_locations()
if not keyword_locations:
self.log_message(f"未找到任何包含关键字的文字位置 "+ "\n")
self.log_message(f"找到 {len(keyword_locations)} 个包含关键字的文字位置 "+ "\n")
# 先尝试使用pyautogui进行所有点击
pyautogui_success = self.click_all_locations_with_pyautogui(keyword_locations)
# 如果pyautogui点击失败,尝试JavaScript点击
if not pyautogui_success:
self.log_message(f"pyautogui点击效果不佳,尝试JavaScript点击所有位置... "+ "\n")
javascript_success = self.click_all_locations_with_javascript(keyword_locations)
# 等待下载完成
self.log_message(f"等待下载完成... "+ "\n")
# 保存到excel 规则3 链接0 时间2 截图 关键字 包名 大小 包地址
self.get_save_excel(guize,url,gxshijian,tupian,key_word,gx_down_path)
# 检查是否有新文件下载
# 启动监控线程
self.start_monitoring(gx_down_path)
time.sleep(2)
except IndexError:
continue
self.log_message(f"执行完成"+ "\n")
# self.close_driver()
def main():
root = tk.Tk()
app = FOFASearchGUI(root)
root.mainloop()
if __name__ == "__main__":
main()
】
运行日志区域【self.log_message("")】及时更新消息。下载速度区域及时更新下载进度条。而不是执行完成后才一次更新全部。