Android之 UI主线程ZT

本文介绍了Android应用程序中的UI更新机制,包括主线程(UIThread)的作用、如何使用Handler在子线程与主线程间传递消息以避免阻塞主线程并正确更新UI。
在一个Android 程序开始运行的时候,会单独启动一个Process。默认的情况下,所有这个程序中的Activity或者Service(Service和Activity只是Android提供的Components中的两种,除此之外还有Content Provider和Broadcast Receiver)都会跑在这个Process。

一个Android 程序默认情况下也只有一个Process,但一个Process下却可以有许多个Thread。

在这么多Thread当中,有一个Thread,我们称之为UI Thread。UI Thread在Android程序运行的时候就被创建,是一个Process当中的主线程Main Thread,主要是负责控制UI界面的显示、更新和控件交互。在Android程序创建之初,一个Process呈现的是单线程模型,所有的任务都在一个线程中运行。因此,我们认为,UI Thread所执行的每一个函数,所花费的时间都应该是越短越好。而其他比较费时的工作(访问网络,下载数据,查询数据库等),都应该交由子线程去执行,以免阻塞主线程。


那么,UI Thread如何和其他Thread一起工作呢?常用方法是:

诞生一个主线程的Handler物件,当做Listener去让子线程能将讯息Push到主线程的Message Quene里,以便触发主线程的handlerMessage()函数,让主线程知道子线程的状态,并在主线程更新UI。


例如,在子线程的状态发生变化时,我们需要更新UI。如果在子线程中直接更新UI,通常会抛出下面的异常:

11-07 13:33:04.393: ERROR/JavaBinder(1029):android.view.ViewRoot$CalledFromWrongThreadException:Only the original thread that created a view hierarchy can touch its views.

意思是,无法在子线程中更新UI。为此,我们需要通过Handler物件,通知主线程Ui Thread来更新界面。

如下,首先创建一个Handler,来监听Message的事件:

private final int UPDATE_UI = 1;
private Handler mHandler = new MainHandler();

private class MainHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_UI: {
Log.i("TTSDeamon", "UPDATE_UI");
showTextView.setText(editText.getText().toString());
ShowAnimation();
break;
}
default:
break;
}
}
}

或者

private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_UI: {
Log.i("TTSDeamon", "UPDATE_UI");
showTextView.setText(editText.getText().toString());
ShowAnimation();
break;
}
default:
break;
}
}
}


当子线程的状态发生变化,则在子线程中发出Message,通知更新UI。

mHandler.sendEmptyMessageDelayed(UPDATE_UI, 0);


在我们的程序中,很多Callback方法有时候并不是运行在主线程当中的,所以如果在Callback方法中更新UI失败,也可以采用上面的方法。
package com.weishitechsub.kdcxqwb.fragment.Adapter; import android.Manifest; import android.app.AlertDialog; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.provider.Settings; import android.text.TextUtils; import android.view.View; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; import com.chad.library.adapter.base.BaseQuickAdapter; import com.chad.library.adapter.base.viewholder.BaseViewHolder; import com.weishitechsub.kdcxqwb.R; import com.weishitechsub.kdcxqwb.bean.ListBean; import com.weishitechsub.kdcxqwb.utils.PackageNotificationSender; import com.weishitechsub.kdcxqwb.utils.ReminderManager; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; public class MyCourierAdapter extends BaseQuickAdapter<ListBean, BaseViewHolder> { private static final String TAG = "MyCourierAdapter"; private List<ListBean> mTrackList; private Context context; // 存储:用户手动开启了提醒的单号 private final Set<String> remindedEnabled = ConcurrentHashMap.newKeySet(); // 存储:每个单号最后一次被提醒到的状态(用于去重) private final Map<String, String> remindedStates = new ConcurrentHashMap<>(); private Handler mainHandler = new Handler(Looper.getMainLooper()); private static final String PREF_NAME = "courier_reminder"; int type; // 缓存当前列表数据,用于根据单号查找最新状态 private List<ListBean> currentList; public MyCourierAdapter(List<ListBean> list, Context context, int type) { super(R.layout.my_courier_adapter, list); this.context = context; this.type = type; ReminderManager.getInstance(context); // 初始化单例 } @Override protected void convert(@NonNull BaseViewHolder baseViewHolder, ListBean dataBean) { String number = dataBean.getNum(); baseViewHolder.setText(R.id.tv_number, number); baseViewHolder.setText(R.id.tv_address, dataBean.getContext()); if (type == 1) { baseViewHolder.getView(R.id.tv_remind).setVisibility(View.GONE); baseViewHolder.getView(R.id.iv_remind).setVisibility(View.GONE); } else { baseViewHolder.getView(R.id.tv_remind).setVisibility(View.VISIBLE); baseViewHolder.getView(R.id.iv_remind).setVisibility(View.VISIBLE); } // 设置快递公司信息图标 if (dataBean.getType() != null) { String trackName = getTrackName(dataBean.getType()); baseViewHolder.setText(R.id.tv_type, trackName); ImageView logo = baseViewHolder.getView(R.id.iv_logo); LinearLayout line = baseViewHolder.getView(R.id.line); setCompanyLogo(trackName, logo, line); } // ===== 获取状态文本 ===== String state = dataBean.getState(); String stateText = getStateText(state); baseViewHolder.setText(R.id.tv_state, stateText); ImageView ivRemind = baseViewHolder.getView(R.id.iv_remind); // === 统一处理所有状态分支(推荐:单一入口,避免遗漏)=== if (state == null || TextUtils.isEmpty(stateText)) { // 情况1:状态为空或无效 ivRemind.setEnabled(false); ivRemind.setAlpha(0.4f); refreshRemindIcon(ivRemind, false); ivRemind.setOnClickListener(v -> { Toast.makeText(context, "该快递状态异常,暂不支持提醒功能", Toast.LENGTH_SHORT).show(); }); } else if ("3".equals(state) || "4".equals(state) || "6".equals(state)) { // 情况2:终止状态(签收/拒签/退回) ivRemind.setEnabled(false); ivRemind.setAlpha(0.5f); refreshRemindIcon(ivRemind, false); ivRemind.setOnClickListener(v -> { String tip = "3".equals(state) ? "该快递已签收,无需提醒。" : "4".equals(state) ? "该快递已拒签,订单结束。" : "该快递已被退回。"; Toast.makeText(context, tip, Toast.LENGTH_SHORT).show(); }); // 自动关闭提醒(如果之前开启了) remindedEnabled.remove(number); remindedStates.put(number, state); } else { // 情况3:正常流转状态 → 允许开启提醒 // ===== 状态变更检测 & 发送通知 ===== if (remindedEnabled.contains(number)) { String oldState = remindedStates.get(number); if (oldState != null && !state.equals(oldState)) { String company = getTrackName(dataBean.getType()); sendStatusNotification(number, company, getStateText(state), getNotifyContent(state)); } remindedStates.put(number, state); } // 刷新图标 boolean isCurrentlyReminded = ReminderManager.getInstance(context).isReminded(number); refreshRemindIcon(ivRemind, isCurrentlyReminded); // 设置点击事件 ivRemind.setEnabled(true); ivRemind.setAlpha(1.0f); ivRemind.setOnClickListener(v -> { boolean nowEnabled = ReminderManager.getInstance(context).isReminded(number); if (nowEnabled) { ReminderManager.getInstance(context).disableReminder(number); Toast.makeText(context, "已关闭提醒: " + number, Toast.LENGTH_SHORT).show(); } else { enableReminderWithPermissionCheck(dataBean); } refreshRemindIcon(ivRemind, !nowEnabled); }); } // 可选:缓存当前列表用于外部查询 if (currentList == null) { currentList = new ArrayList<>(); } } // 检查权限后开启提醒,并发送“当前状态”的通知 private void enableReminderWithPermissionCheck(ListBean dataBean) { String number = dataBean.getNum(); String state = dataBean.getState(); String type = dataBean.getType(); // 权限检查... if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { showNotificationPermissionDialog(); return; } } // 启用提醒(自动保存到 SP) ReminderManager.getInstance(context).enableReminder(number); Toast.makeText(context, "已开启提醒: " + number, Toast.LENGTH_SHORT).show(); // 发送欢迎通知 String companyName = getTrackName(type); sendStatusNotification(number, companyName, getStateText(state), getNotifyContent(state)); } // 刷新提醒按钮图标 private void refreshRemindIcon(ImageView ivRemind, boolean isEnabled) { if (isEnabled) { ivRemind.setImageResource(R.mipmap.button_on); // 🔔 } else { ivRemind.setImageResource(R.mipmap.button_off); // 🔕 } } // 获取状态中文文本 private String getStateText(String state) { switch (state) { case "0": return "在途"; case "5": return "派件"; case "3": return "签收"; case "6": return "退回"; case "4": return "退签"; case "1": return "揽收"; case "7": return "转投"; case "2": return "疑难"; case "8": return "清关"; case "10": return "待清关"; case "11": return "清关中"; case "12": return "已清关"; case "13": return "清关异常"; default: return null; } } // 获取对应状态的通知内容 private String getNotifyContent(String state) { switch (state) { case "0": return "您的快递已在运输途中,正在路上~"; case "5": return "快递员正在为您派送,请注意查收!"; case "3": return "您的快递已签收,感谢使用!"; case "6": return "很遗憾,您的快递因故被退回。"; case "4": return "您已拒签该快递,订单已完成。"; default: return "快递因地址问题已转投其他网点。"; } } // 发送状态变更通知 private void sendStatusNotification(String number, String company, String titleSuffix, String content) { PackageNotificationSender sender = new PackageNotificationSender(context); int id = ("status_" + number).hashCode() & Integer.MAX_VALUE; id = (id % 1_000_000) + 2000; // 控制 ID 范围,避免冲突 sender.sendCustomNotification(id, "📌 快递" + titleSuffix, content, number, company); } // 根据 type 查找快递公司名称 private String getTrackName(String type) { if (mTrackList != null) { for (ListBean item : mTrackList) { if (item.getNum() != null && type != null && TextUtils.equals(item.getNum(), type)) { return item.getCom(); } } } return ""; } // 设置外部数据源 public void setTrackList(List<ListBean> list) { this.mTrackList = list; } // 缓存当前列表(供开启提醒时查询状态) @Override public void setNewInstance(List<ListBean> list) { super.setNewInstance(list); this.currentList = list; } // 可选:暴露方法供外部查询某单号是否开启提醒 public boolean isReminderEnabled(String number) { return remindedEnabled.contains(number); } /** * 根据快递公司名称设置对应的 Logo 图标 */ private void setCompanyLogo(String name, ImageView logo, LinearLayout line) { if (name == null || name.isEmpty()) { logo.setVisibility(View.GONE); line.setVisibility(View.VISIBLE); return; } switch (name) { case "邮政快递包裹": logo.setImageResource(R.mipmap.img_yz_bg); logo.setVisibility(View.VISIBLE); line.setVisibility(View.GONE); break; case "京东物流": logo.setImageResource(R.mipmap.img_jd_bg); logo.setVisibility(View.VISIBLE); line.setVisibility(View.GONE); break; case "圆通快递": logo.setImageResource(R.mipmap.img_yt_bg); logo.setVisibility(View.VISIBLE); line.setVisibility(View.GONE); break; case "中通快递": logo.setImageResource(R.mipmap.img_zt_bg); logo.setVisibility(View.VISIBLE); line.setVisibility(View.GONE); break; case "顺丰速运": logo.setImageResource(R.mipmap.img_sf_bg); logo.setVisibility(View.VISIBLE); line.setVisibility(View.GONE); break; case "韵达快递": logo.setImageResource(R.mipmap.img_yd_bg); logo.setVisibility(View.VISIBLE); line.setVisibility(View.GONE); break; case "申通快递": logo.setImageResource(R.mipmap.img_st_bg); logo.setVisibility(View.VISIBLE); line.setVisibility(View.GONE); break; case "EMS": logo.setImageResource(R.mipmap.img_ems_bg); logo.setVisibility(View.VISIBLE); line.setVisibility(View.GONE); break; default: logo.setVisibility(View.GONE); line.setVisibility(View.VISIBLE); break; } } public void loadSavedStates() { // 如果 ReminderManager 已经负责加载,则不需要此方法 // 或者你可以让它触发一次同步 ReminderManager manager = ReminderManager.getInstance(context); // manager.loadFromSP(); // 如果不是自动加载,手动调用 } // MyCourierAdapter.java 中新增 public List<ListBean> getRemindedItems() { List<ListBean> result = new ArrayList<>(); if (currentList == null) return result; Set<String> remindedNumbers = ReminderManager.getInstance(context).getRemindedNumbers(); for (ListBean bean : currentList) { if (remindedNumbers.contains(bean.getNum())) { result.add(bean); } } return result; } private void showNotificationPermissionDialog() { if (context == null) return; new AlertDialog.Builder(context) .setTitle("需要通知权限") .setMessage("请允许本应用发送快递状态提醒通知。\n\n进入【设置】→【通知】中开启权限。") .setPositiveButton("去开启", (dialog, which) -> { Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.setData(Uri.fromParts("package", context.getPackageName(), null)); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // 防止在非 Activity 上下文中启动失败 context.startActivity(intent); }) .setNegativeButton("取消", (dialog, which) -> { Toast.makeText(context, "未授予通知权限,无法开启提醒", Toast.LENGTH_SHORT).show(); }) .create() .show(); } } 这个判断 if (state == null || TextUtils.isEmpty(stateText)) { // 情况1:状态为空或无效 ivRemind.setEnabled(false); ivRemind.setAlpha(0.4f); refreshRemindIcon(ivRemind, false); ivRemind.setOnClickListener(v -> { Toast.makeText(context, "该快递状态异常,暂不支持提醒功能", Toast.LENGTH_SHORT).show(); }); } 还是不提醒
最新发布
11-29
【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("")】及时更新消息。下载速度区域及时更新下载进度条。而不是执行完成后才一次更新全部。
11-21
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值