Files
ca_auto_table/spider/main.py

862 lines
30 KiB
Python
Raw Normal View History

2025-11-20 11:42:18 +08:00
import random
import time
2025-11-28 09:17:59 +08:00
from datetime import datetime
2025-11-20 11:42:18 +08:00
from DrissionPage import Chromium
from loguru import logger
from work import get_random_canada_info
from mail_ import mail_
from bit_browser import bit_browser
2025-11-20 21:42:20 +08:00
from api import api
2025-11-27 16:22:07 +08:00
from proxys import proxy_list
2025-11-28 16:02:13 +08:00
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed
2025-11-20 11:42:18 +08:00
2025-11-21 01:56:01 +08:00
2025-11-20 11:42:18 +08:00
class Auto:
2025-11-21 01:56:01 +08:00
def __init__(self, http: str = None):
2025-11-20 11:42:18 +08:00
self.browser = Chromium(http)
self.tab = self.browser.latest_tab
pass
# cf打码
2025-11-21 01:56:01 +08:00
def solve_cloudflare(self, is_ok: bool = False):
2025-11-20 11:42:18 +08:00
tab = self.browser.latest_tab
2025-11-21 11:51:52 +08:00
for _ in range(5):
tab.wait(1)
2025-11-21 12:17:41 +08:00
res = tab.ele(
't:h1@text()=Sorry, you have been blocked', timeout=1)
2025-11-21 11:51:52 +08:00
if res:
logger.error("Cloudflare验证失败")
return False
2025-11-21 12:17:41 +08:00
2025-11-20 11:42:18 +08:00
try:
shadow1 = tab.ele(
'x://*[@name="cf-turnstile-response"]').parent().shadow_root
iframe = shadow1.get_frame(1)
if iframe:
logger.debug("找到Cloudflare iframe")
shadow2 = iframe.ele('x:/html/body').shadow_root
if shadow2:
logger.debug("找到Cloudflare iframe body shadow root")
2025-11-20 23:39:38 +08:00
status = shadow2.ele(
2025-11-21 01:56:01 +08:00
'x://span[text()="Verifying..."]', timeout=1.5)
2025-11-20 23:39:38 +08:00
if status:
tab.wait(3)
2025-11-20 11:42:18 +08:00
status = shadow2.ele(
2025-11-21 01:56:01 +08:00
'x://span[text()="Success!"]', timeout=1.5)
2025-11-20 11:42:18 +08:00
if status:
logger.debug("Cloudflare验证成功")
return True
checkbox = shadow2.ele(
2025-11-21 01:56:01 +08:00
'x://input[@type="checkbox"]', timeout=1.5)
2025-11-20 11:42:18 +08:00
if checkbox:
checkbox.click()
logger.debug("点击Cloudflare复选框")
2025-11-20 21:42:20 +08:00
tab.wait(3)
2025-11-20 11:42:18 +08:00
logger.debug("重新获取状态")
# return False
except Exception as e:
# logger.error(f"处理Cloudflare异常: {e}")
2025-11-21 01:56:01 +08:00
if is_ok:
logger.debug(f"cloudflare处理通过: {e}")
return True
return self.solve_cloudflare(is_ok=True)
2025-11-20 11:42:18 +08:00
tab.wait(1)
return False
# 打开URL
def open_url(self, url: str):
self.tab.get(url)
# 等待进入首页
def wait_home(self):
logger.debug("等待进入首页")
jc = 0
while True:
2025-11-20 21:42:20 +08:00
if jc > 3:
2025-11-20 11:42:18 +08:00
logger.error("等待进入首页超过5次未成功")
return False
self.tab.wait(1)
# 判断cf是否通过
bol = self.solve_cloudflare()
if not bol:
logger.debug("Cloudflare验证失败.")
2025-11-21 11:51:52 +08:00
# 刷新网页
self.tab.refresh()
self.tab.wait(1.5)
jc += 1
2025-11-20 11:42:18 +08:00
continue
else:
logger.debug("Cloudflare验证成功.")
self.tab.wait(1.5)
2025-11-21 01:56:01 +08:00
bol = self.tab.ele(
2025-11-21 12:17:41 +08:00
't:h1@text()=Sorry, you have been blocked', timeout=1)
2025-11-20 23:39:38 +08:00
if bol:
logger.debug("ip被ban秒")
return False
2025-11-26 09:15:27 +08:00
bol = self.tab.ele(
't:div@text():ERR_TIMED_OUT', timeout=1)
if bol:
logger.debug("刷新网页")
self.tab.refresh()
self.tab.wait(1.5)
2025-11-21 01:56:01 +08:00
bol = self.tab.ele(
't:div@text():ERR_SSL_PROTOCOL_ERROR', timeout=1)
2025-11-20 23:57:42 +08:00
if bol:
logger.debug("刷新网页")
self.tab.refresh()
self.tab.wait(1.5)
2025-11-23 10:24:50 +08:00
bol = self.tab.ele(
't:div@text():ERR_SOCKS_CONNECTION_FAILED', timeout=1)
if bol:
logger.debug("刷新网页")
self.tab.refresh()
self.tab.wait(1.5)
2025-11-20 11:42:18 +08:00
html = self.tab.url
logger.debug(f"当前URL: {html}")
if 'https://veritaconnect.ca/canadianbreadsettlement/en-us' == html:
logger.debug("成功进入首页")
return True
jc += 1
# 点击continue按钮
def click_continue(self, bl: bool = False):
logger.debug("点击Continue按钮")
jc = 0
while True:
2025-11-20 21:42:20 +08:00
if jc > 3:
2025-11-20 11:42:18 +08:00
logger.error("点击Continue按钮超过5次未成功")
return False
try:
continue_button = self.tab.ele(
't:button@text():Continue', timeout=1)
if continue_button:
2025-11-22 14:15:57 +08:00
jc += 1
2025-11-21 00:20:03 +08:00
# 滚动到最底部
self.tab.scroll.to_bottom()
self.tab.wait(1)
2025-11-20 11:42:18 +08:00
# 判断cf是否通过
bol = self.solve_cloudflare()
if not bol:
logger.debug("Cloudflare验证失败..")
2025-11-21 11:51:52 +08:00
self.tab.refresh()
self.tab.wait(1.5)
2025-11-20 11:42:18 +08:00
continue
else:
logger.debug("Cloudflare验证成功..")
2025-11-21 02:04:52 +08:00
self.tab.wait(3)
2025-11-20 11:42:18 +08:00
continue_button.click()
logger.debug("点击Continue按钮成功")
self.tab.wait(1.5)
2025-11-21 11:51:52 +08:00
# bol = self.tab.ele('@text():Loading', timeout=1)
# if bol:
# logger.debug("Loading...")
# if bl:
# logger.debug("多次异常界面, 结束继续点击")
# return False
# logger.debug("异常界面")
# self.tab.wait(1)
# return self.click_continue(bl=True)
2025-11-21 12:17:41 +08:00
2025-11-26 09:15:27 +08:00
bol = self.tab.ele(
't:div@text():ERR_TIMED_OUT', timeout=1)
if bol:
logger.debug("刷新网页")
self.tab.refresh()
self.tab.wait(1.5)
2025-11-22 14:15:57 +08:00
bol = self.tab.ele(
't:div@text():ERR_SSL_PROTOCOL_ERROR', timeout=1)
if bol:
logger.debug("刷新网页")
self.tab.refresh()
self.tab.wait(1.5)
2025-11-23 10:24:50 +08:00
bol = self.tab.ele(
't:div@text():ERR_SOCKS_CONNECTION_FAILED', timeout=1)
if bol:
logger.debug("刷新网页")
self.tab.refresh()
self.tab.wait(1.5)
2025-11-21 12:17:41 +08:00
bol = self.tab.ele(
't:h1@text()=Sorry, you have been blocked', timeout=1)
if bol:
logger.debug("ip被ban秒")
# 刷新网页
self.tab.refresh()
self.tab.wait(1.5)
2025-11-21 12:41:26 +08:00
2025-11-21 01:56:01 +08:00
bol = self.tab.ele(
't:h2@text()=You are being rate limited', timeout=1)
2025-11-20 21:42:20 +08:00
if bol:
logger.debug("被限流, 退出")
return False
2025-11-20 11:42:18 +08:00
bol = self.tab.ele(
't:li@text():There was a problem, please try again.', timeout=1)
if bol:
if bl:
logger.debug("多次异常界面, 结束继续点击")
return False
logger.debug("异常界面")
self.tab.wait(1)
return self.click_continue(bl=True)
html = self.tab.url
logger.debug(f"当前URL: {html}")
if 'https://veritaconnect.ca/canadianbreadsettlement/en-us/Claimant/UnknownClaimForm' in html:
logger.debug("成功进入问卷界面")
return True
2025-11-26 09:15:27 +08:00
jc += 1
2025-11-20 11:42:18 +08:00
except Exception as e:
logger.error(f"点击Continue按钮异常: {e}")
2025-11-30 00:19:46 +08:00
jc += 1
if jc > 3:
return False
2025-11-20 11:42:18 +08:00
self.tab.wait(1)
# 随机取城市
2025-11-21 01:56:01 +08:00
def get_random_city(self, province: str | None = None):
2025-11-20 11:42:18 +08:00
cities = {
"Alberta": ["Calgary", "Edmonton"],
"British Columbia": ["Vancouver"],
# "Manitoba": ["Winnipeg", "Rochester"],
# "New Brunswick": ["Fredericton", "Moncton"],
# "Newfoundland and Labrador": ["St. John's", "Halifax"],
"Nova Scotia": ["Halifax"],
"Ontario": ["Toronto"],
# "Prince Edward Island": ["Charlottetown", "St. John's"],
# "Quebec": ["Quebec City", "Montreal"],
# "Saskatchewan": ["Saskatoon", "Regina"],
}
if province is None:
province = random.choice(list(cities.keys()))
2025-11-21 01:56:01 +08:00
return province, random.choice(cities.get(province, []))
2025-11-20 21:42:20 +08:00
2025-11-21 11:51:52 +08:00
def get_province_by_city(self) -> str | None:
2025-11-20 21:42:20 +08:00
"""
根据城市名称解析对应省份
参数:
city (str): 城市名称例如 `Calgary``Edmonton`
返回值:
str | None: 对应的省份名称未匹配返回 None
"""
mapping = {
"Calgary": "Alberta",
"Edmonton": "Alberta",
"Vancouver": "British Columbia",
"Halifax": "Nova Scotia",
"Toronto": "Ontario",
2025-11-21 11:51:52 +08:00
"Ottawa": "Ontario",
"Mississauga": "Ontario",
"Brampton": "Ontario",
"Hamilton": "Ontario",
"Kitchener": "Ontario",
"London": "Ontario",
"Markham": "Ontario",
"Vaughan": "Ontario",
"Windsor": "Ontario",
"Oshawa": "Ontario",
"Brantford": "Ontario",
"Barrie": "Ontario",
"Sudbury": "Ontario",
"Kingston": "Ontario",
"Guelph": "Ontario",
"Cambridge": "Ontario",
"Sarnia": "Ontario",
"Peterborough": "Ontario",
"Waterloo": "Ontario",
"Belleville": "Ontario",
"Brockville": "Ontario",
"Burlington": "Ontario",
"Cornwall": "Ontario",
"Kawartha Lakes": "Ontario",
"North Bay": "Ontario",
"Orillia": "Ontario",
"Pickering": "Ontario",
"Sault Ste. Marie": "Ontario",
"Stratford": "Ontario",
"Durham": "Ontario",
"Norfolk County": "Ontario",
"Prince Edward County": "Ontario",
"Quinte West": "Ontario",
"St. Catharines": "Ontario",
"Welland": "Ontario",
"Thorold": "Ontario",
"Niagara Falls": "Ontario",
"Pelham": "Ontario",
"Port Colborne": "Ontario",
2025-11-20 21:42:20 +08:00
}
2025-11-21 11:51:52 +08:00
# 随机返回一条 key 和 value
return random.choice(list(mapping.items()))
2025-11-20 21:42:20 +08:00
# 随机实物
2025-11-21 12:17:41 +08:00
2025-11-21 01:56:01 +08:00
def get_random_food(self, city: str, shop: str) -> list[str]:
2025-11-20 21:42:20 +08:00
"""
随机选择 1~2 种食物类别并为每个类别至少选择 1 个具体产品
参数:
shop (str): 商店名称当前未使用占位参数
返回值:
list[str]: 随机选取的产品名称列表
"""
categories = [
[
'Wonder Bread White',
'Villaggio White Bread',
'No Name Sliced White Bread',
"President's Choice White Sliced Bread",
],
[
"Ben's Original Whole Wheat Bread",
"POM Whole Wheat Bread",
"Silver Hills Bakery Whole Wheat Sliced Bread",
"Country Harvest Whole Wheat Bread",
],
[
"Wonder Bread Hot Dog Buns",
"Villaggio Hamburger Buns",
"Dempster's Dinner Rolls",
"No Frills Hot Dog Buns",
],
[
"Stonemill Bakehouse Bagels",
"Wonder Bagels",
"Montreal Bagels (pre-packaged, e.g., St. Lawrence brand)",
"President's Choice Bagels",
],
[
"Silver Hills Multi-Grain Sliced Bread",
"POM Multi-Grain Bread",
"Country Harvest Multi-Grain Loaf",
],
[
"President's Choice French Stick",
"Dempster's Italian Style Bread",
"Wonder Italian Bread",
"Villaggio Country Style Loaf",
],
]
# 随机选择 1~2 个类别(不重复)
category_count = random.randint(1, 2)
chosen_categories = random.sample(categories, k=category_count)
# 每个类别至少选择 1 个产品,最多选择 3 个以避免过多
selected_products: list[str] = []
for cat in chosen_categories:
max_pick = min(3, len(cat))
pick_count = random.randint(1, max_pick)
selected_products.extend(random.sample(cat, k=pick_count))
logger.debug(f"随机选择的产品: {selected_products}")
2025-11-21 01:40:47 +08:00
text = f'{shop}, {city} buy: '
2025-11-20 21:42:20 +08:00
for p in selected_products:
text += f'{p} * {random.randint(1, 3)}, '
text = text[:-2]
text = text + '.'
logger.debug(f'随机选择的产品文本: {text}')
return text
2025-11-20 11:42:18 +08:00
# 填写问卷
2025-11-21 11:51:52 +08:00
def fill_questionnaire(self):
2025-11-20 21:42:20 +08:00
"""
2025-11-21 11:51:52 +08:00
完成问卷填写
2025-11-20 21:42:20 +08:00
参数:
city (str): 线程启动时传入的城市名称用于匹配省份并填写数据
"""
try:
2025-11-21 11:51:52 +08:00
city, province = self.get_province_by_city()
2025-11-20 21:42:20 +08:00
if province is None:
2025-11-21 11:51:52 +08:00
logger.error(f"未找到城市对应省份")
2025-11-20 21:42:20 +08:00
return
j = 0
while True:
2025-11-21 01:56:01 +08:00
if j > 3:
return False
2025-11-20 21:42:20 +08:00
info = get_random_canada_info(province, city)
if len(info.get('postcode')) > 5:
break
j += 1
first_name = info["firstname"]
last_name = info["lastname"]
# 将生日格式从 '8/28/1995' 转为 'yyyy-mm-dd'日月不足两位补0
birthday = info["birthday"]
current_address = info["address_str"]
# 保持使用线程传入的城市与解析出的省份
postal_code = info["postcode"]
email = mail_.email_create_random()
phone = info["phone"]
shop = api.get_random_shop()
if shop is None:
return None
street = shop.get('street')
if street is None:
return None
2025-11-21 01:40:47 +08:00
text = self.get_random_food(shop.get('city'), street)
2025-11-20 21:42:20 +08:00
# 人数
person_count = str(random.randint(3, 5))
logger.debug("填写问卷")
self.tab.wait(0.1)
logger.debug(f"填写first_name: {first_name}")
self.tab.ele('t:input@id=FirstName').set.value(first_name)
self.tab.wait(0.1)
logger.debug(f"填写last_name: {last_name}")
self.tab.ele('t:input@id=LastName').set.value(last_name)
self.tab.wait(0.1)
logger.debug(f"填写birthday: {birthday}")
self.tab.ele('t:input@id=DateOfBirth').set.value(birthday)
self.tab.wait(0.1)
logger.debug(f"填写current_address: {current_address}")
self.tab.ele('t:input@id=AddressLine1').set.value(current_address)
self.tab.wait(0.1)
logger.debug(f"填写city: {city}")
self.tab.ele('t:input@id=City').set.value(city)
self.tab.wait(0.1)
logger.debug(f"填写province: {province}")
self.tab.ele(
't:select@id=CanProv').ele(f't:option@text()={province}').click()
2025-11-21 01:56:01 +08:00
self.tab.wait(0.1)
2025-11-20 21:42:20 +08:00
logger.debug(f"填写postal_code: {postal_code}")
self.tab.ele('t:input@id=CanPostal').set.value(postal_code)
self.tab.wait(0.1)
logger.debug(f"填写NumberOfAdults: {person_count}")
self.tab.ele(
't:select@id=NumberOfAdults').ele(f't:option@text()={person_count}').click()
self.tab.wait(0.1)
logger.debug(f"选择地址没变")
self.tab.eles('t:input@id=IsDifferentAddress')[1].click()
self.tab.wait(0.1)
logger.debug(f"填写email: {email}")
self.tab.ele('t:input@id=EmailAddress').set.value(email)
self.tab.wait(0.1)
logger.debug(f"填写ConfirmEmailAddress: {email}")
self.tab.ele('t:input@id=ConfirmEmailAddress').set.value(email)
self.tab.wait(0.1)
logger.debug(f"填写phone: {phone}")
self.tab.ele('t:input@id=PhoneNumber').set.value(phone)
self.tab.wait(0.1)
logger.debug(f"选择同意条款")
self.tab.ele('t:input@id=IVerify').click()
self.tab.wait(0.1)
logger.debug(f"选择没有申请过")
self.tab.eles('t:input@id=IsCompensated')[1].click()
self.tab.wait(0.1)
logger.debug(f"填写text: {text}")
self.tab.ele('t:textarea@id=MetaAnswerA').set.value(text)
self.tab.wait(0.1)
logger.debug(f"勾选同意我的名字")
self.tab.ele('t:input@id=IDeclare').click()
self.tab.wait(0.1)
logger.debug(f"填写PrintName: {last_name+' '+first_name}")
self.tab.ele(
't:input@id=PrintName').set.value(last_name+' '+first_name)
self.tab.wait(0.1)
2025-11-21 12:17:41 +08:00
return self.submit_file(first_name, last_name, birthday, current_address, city, phone, postal_code, province, email, text)
2025-11-25 00:50:09 +08:00
2025-11-20 21:42:20 +08:00
except Exception as e:
logger.error(f"填写问卷失败: {e}")
2025-11-20 11:42:18 +08:00
2025-11-21 12:17:41 +08:00
# 提交问卷
def submit_file(self, first_name: str, last_name: str, birthday: str, current_address: str, city: str, phone: str, postal_code: str, province: str, email: str, text: str):
jc = 0
while True:
if jc >= 3:
logger.error("提交问卷失败")
return False
res = self.tab.ele(
't:h2@text()=CLAIM SUBMISSION CONFIRMATION', timeout=3)
if res:
logger.info("提交问卷成功")
2025-11-25 00:50:09 +08:00
res = self.tab.ele('@text():Your claim number: ')
if res:
2025-11-25 01:07:47 +08:00
logger.info(f"反馈地址: {res.text}")
text =f"{text}----{res.text}"
2025-11-25 00:50:09 +08:00
status = True
else:
status=False
2025-11-21 12:17:41 +08:00
api.create_info(
first_name=first_name,
last_name=last_name,
birthday=birthday,
current_address=current_address,
city=city,
phone=phone,
postal_code=postal_code,
province=province,
email=email,
2025-11-25 00:50:09 +08:00
text=text,
status=status
2025-11-21 12:17:41 +08:00
)
return True
2025-11-25 00:50:09 +08:00
2025-11-26 09:15:27 +08:00
bol = self.tab.ele(
't:div@text():ERR_TIMED_OUT', timeout=1)
if bol:
logger.debug("刷新网页")
self.tab.refresh()
self.tab.wait(1.5)
2025-11-22 14:15:57 +08:00
bol = self.tab.ele(
't:div@text():ERR_SSL_PROTOCOL_ERROR', timeout=1)
if bol:
logger.debug("刷新网页")
self.tab.refresh()
self.tab.wait(1.5)
2025-11-23 10:24:50 +08:00
bol = self.tab.ele(
't:div@text():ERR_SOCKS_CONNECTION_FAILED', timeout=1)
if bol:
logger.debug("刷新网页")
self.tab.refresh()
self.tab.wait(1.5)
2025-11-21 12:17:41 +08:00
bol = self.solve_cloudflare()
if not bol:
logger.debug("Cloudflare验证失败.")
self.tab.wait(1)
else:
logger.debug("Cloudflare验证成功.")
logger.debug(f"点击Submit按钮")
self.tab.ele('t:button@text():Submit').click()
self.tab.wait(3)
jc += 1
2025-11-20 11:42:18 +08:00
2025-11-28 16:02:13 +08:00
def parse_proxy(proxy: str) -> tuple[str, int, str, str] | None:
2025-11-21 11:51:52 +08:00
"""
2025-11-28 16:02:13 +08:00
解析代理字符串为四元组 `(host, port, user, pwd)`
参数:
proxy: 形如 `host:port:user:pwd`
2025-11-21 11:51:52 +08:00
返回值:
2025-11-28 16:02:13 +08:00
(host, port, user, pwd) None格式错误
2025-11-21 11:51:52 +08:00
"""
try:
2025-11-28 16:02:13 +08:00
host, port, user, pwd = proxy.split(":", 3)
return host, int(port), user, pwd
2025-11-21 11:51:52 +08:00
except Exception:
2025-11-28 16:02:13 +08:00
logger.error(f"代理格式错误: {proxy}")
2025-11-21 11:51:52 +08:00
return None
2025-11-20 11:42:18 +08:00
2025-11-25 00:50:09 +08:00
2025-11-28 16:02:13 +08:00
def create_fingerprint_browser(proxy: str) -> tuple[str, str] | None:
2025-11-21 12:41:26 +08:00
"""
2025-11-28 16:02:13 +08:00
创建指纹浏览器并打开窗口返回 `(browser_id, debugger_http)`
参数:
proxy: 代理字符串
2025-11-21 12:41:26 +08:00
返回值:
2025-11-28 16:02:13 +08:00
(browser_id, http) None失败
2025-11-21 12:41:26 +08:00
"""
2025-11-28 16:02:13 +08:00
info = parse_proxy(proxy)
if info is None:
return None
host, port, user, pwd = info
try:
browser_id = bit_browser.bit_browser_create(
remark=f"{user}",
proxy_type="socks5",
host=host,
port=str(port),
proxy_user=user,
proxy_pwd=pwd,
)
if not browser_id:
return None
logger.info(f"创建指纹浏览器成功: {browser_id}")
2025-11-30 00:19:46 +08:00
time.sleep(1)
2025-11-28 16:02:13 +08:00
http = bit_browser.bit_browser_open(browser_id)
if not http:
return None
logger.info(f"打开指纹浏览器成功: {browser_id}")
return browser_id, http
except Exception as e:
logger.error(f"创建指纹浏览器失败: {e}")
return None
2025-11-21 12:41:26 +08:00
2025-11-21 12:17:41 +08:00
2025-11-28 16:02:13 +08:00
def close_and_delete_browser(browser_id: str) -> None:
2025-11-28 09:17:59 +08:00
"""
2025-11-28 16:02:13 +08:00
关闭并删除指定指纹浏览器
2025-11-28 09:17:59 +08:00
2025-11-28 16:02:13 +08:00
参数:
browser_id: 指纹浏览器ID
2025-11-28 09:17:59 +08:00
"""
2025-11-28 16:02:13 +08:00
try:
bit_browser.bit_browser_close(browser_id)
except Exception as e:
logger.warning(f"关闭浏览器失败或已关闭: {browser_id} - {e}")
2025-11-30 00:19:46 +08:00
time.sleep(1)
2025-11-28 16:02:13 +08:00
try:
bit_browser.bit_browser_delete(browser_id)
except Exception as e:
logger.warning(f"删除浏览器失败或已删除: {browser_id} - {e}")
2025-11-21 01:56:01 +08:00
2025-11-28 16:02:13 +08:00
def run_task_with_proxy(proxy: str, stop_event: threading.Event) -> None:
2025-11-20 11:42:18 +08:00
"""
2025-11-28 16:02:13 +08:00
使用代理创建指纹浏览器执行自动化并在结束后清理
2025-11-20 11:42:18 +08:00
参数:
2025-11-28 16:02:13 +08:00
proxy: 代理字符串
2025-11-20 11:42:18 +08:00
"""
2025-11-28 16:02:13 +08:00
browser_id: str | None = None
2025-11-20 11:42:18 +08:00
try:
2025-11-28 16:02:13 +08:00
created = create_fingerprint_browser(proxy)
if not created:
2025-11-28 09:17:59 +08:00
return
2025-11-28 16:02:13 +08:00
browser_id, http = created
if stop_event.is_set():
2025-11-20 11:42:18 +08:00
return
2025-11-28 16:02:13 +08:00
auto = Auto(http=http)
auto.open_url('https://veritaconnect.ca/canadianbreadsettlement/en-us')
if stop_event.is_set():
return
if not auto.wait_home():
return
if stop_event.is_set():
return
if not auto.click_continue():
return
if stop_event.is_set():
2025-11-20 11:42:18 +08:00
return
2025-11-21 11:51:52 +08:00
auto.fill_questionnaire()
2025-11-28 16:02:13 +08:00
except Exception as e:
logger.error(f"执行任务异常: {e}")
2025-11-20 11:42:18 +08:00
finally:
if browser_id:
try:
2025-11-28 16:02:13 +08:00
close_and_delete_browser(browser_id)
except Exception:
pass
2025-11-20 11:42:18 +08:00
2025-11-21 01:56:01 +08:00
2025-11-28 16:02:13 +08:00
def proxy_loop(proxy: str, stop_event: threading.Event) -> None:
2025-11-20 11:42:18 +08:00
"""
2025-11-28 16:02:13 +08:00
为单个代理保持持续运行任务结束后立即重建并再次执行
2025-11-20 11:42:18 +08:00
参数:
2025-11-28 16:02:13 +08:00
proxy: 代理字符串
stop_event: 停止事件用于外部触发退出循环
2025-11-20 11:42:18 +08:00
"""
2025-11-28 16:02:13 +08:00
while not stop_event.is_set():
2025-11-20 11:42:18 +08:00
try:
2025-11-28 16:02:13 +08:00
if is_forbidden_time():
2025-11-30 09:27:15 +08:00
if stop_event.wait(timeout=60):
2025-11-30 00:19:46 +08:00
break
2025-11-28 16:02:13 +08:00
cleanup_all_browsers()
secs = seconds_until(20, 0)
if stop_event.wait(timeout=secs):
break
continue
run_task_with_proxy(proxy, stop_event)
2025-11-20 11:42:18 +08:00
except Exception as e:
2025-11-28 16:02:13 +08:00
logger.error(f"代理循环异常: {proxy} - {e}")
if stop_event.is_set():
break
if stop_event.wait(timeout=0.1):
break
2025-11-20 11:42:18 +08:00
2025-11-21 01:56:01 +08:00
2025-11-28 16:02:13 +08:00
def is_forbidden_time() -> bool:
2025-11-21 11:51:52 +08:00
"""
2025-11-28 16:02:13 +08:00
判断当前是否处于禁跑时段每日 18:30 ~ 20:00本地时间
2025-11-21 11:51:52 +08:00
2025-11-28 16:02:13 +08:00
返回值:
bool: True 表示处于禁跑时段
2025-11-21 11:51:52 +08:00
"""
2025-11-28 16:02:13 +08:00
now = datetime.now()
start = now.replace(hour=18, minute=30, second=0, microsecond=0)
end = now.replace(hour=20, minute=0, second=0, microsecond=0)
return start <= now < end
2025-11-21 11:51:52 +08:00
2025-11-30 09:15:08 +08:00
def wait_until_out_of_forbidden(interval_sec: float = 5.0, stop_event: threading.Event | None = None) -> None:
"""
在禁跑时段内循环等待直到禁跑时段结束
参数:
interval_sec: 轮询间隔秒数
stop_event: 可选停止事件若设置则在等待期间可提前结束
"""
while is_forbidden_time():
if stop_event is not None and stop_event.wait(timeout=interval_sec):
break
time.sleep(interval_sec)
2025-11-21 11:51:52 +08:00
2025-11-28 16:02:13 +08:00
def seconds_until(hour: int, minute: int) -> float:
2025-11-20 11:42:18 +08:00
"""
2025-11-28 16:02:13 +08:00
计算到今天指定时间点的剩余秒数
2025-11-21 11:51:52 +08:00
参数:
2025-11-28 16:02:13 +08:00
hour: 目标小时24小时制
minute: 目标分钟
返回值:
float: 剩余秒数若目标时间已过则为 0
2025-11-20 11:42:18 +08:00
"""
2025-11-28 16:02:13 +08:00
now = datetime.now()
target = now.replace(hour=hour, minute=minute, second=0, microsecond=0)
if target <= now:
return 0.0
return (target - now).total_seconds()
def count_fingerprint_browsers() -> int:
2025-11-21 12:41:26 +08:00
"""
2025-11-28 16:02:13 +08:00
统计当前指纹浏览器数量
2025-11-21 12:41:26 +08:00
2025-11-28 16:02:13 +08:00
返回值:
int: 当前总数量
2025-11-21 12:41:26 +08:00
"""
try:
2025-11-28 16:02:13 +08:00
res = bit_browser.bit_browser_get(0, 100)
data = res.get("data", {}) if isinstance(res, dict) else {}
total = data.get("totalNum")
lst = data.get("list", [])
if isinstance(total, int) and total >= 0:
return total
return len(lst)
except Exception as e:
logger.warning(f"统计指纹浏览器数量失败: {e}")
return 0
def cleanup_all_browsers() -> None:
"""
关闭并删除所有指纹浏览器
"""
try:
res = bit_browser.bit_browser_get(0, 100)
data = res.get("data", {}) if isinstance(res, dict) else {}
lst = data.get("list", [])
ids = [i.get("id") for i in lst if i.get("id")]
for bid in ids:
close_and_delete_browser(bid)
except Exception as e:
logger.warning(f"清理所有指纹浏览器失败: {e}")
2025-11-21 12:41:26 +08:00
2025-11-30 00:19:46 +08:00
def delete_excess_browsers(limit: int) -> None:
"""
删除超出上限的指纹浏览器从列表末尾开始删除
参数:
limit: 允许的最大浏览器数量
"""
try:
res = bit_browser.bit_browser_get(0, 100)
data = res.get("data", {}) if isinstance(res, dict) else {}
lst = data.get("list", [])
ids = [i.get("id") for i in lst if i.get("id")]
count = len(ids)
if count <= limit:
return
excess = count - limit
to_delete = ids[-excess:]
for bid in reversed(to_delete):
close_and_delete_browser(bid)
logger.info(f"已删除超出数量 {excess},当前限制为 {limit}")
except Exception as e:
logger.warning(f"删除超额浏览器失败: {e}")
2025-11-28 16:02:13 +08:00
def monitor_browsers_and_restart(limit: int, stop_event: threading.Event, restart_event: threading.Event) -> None:
2025-11-21 12:41:26 +08:00
"""
2025-11-30 00:19:46 +08:00
3 秒检测指纹浏览器数量超过 `limit` 则从末尾删除超出部分
2025-11-21 12:41:26 +08:00
参数:
2025-11-28 16:02:13 +08:00
limit: 允许的最大浏览器数量通常为代理数量
2025-11-30 00:19:46 +08:00
restart_event: 触发重启的事件当前策略不使用
2025-11-21 12:41:26 +08:00
"""
2025-11-28 16:02:13 +08:00
while not stop_event.is_set():
2025-11-30 00:19:46 +08:00
time.sleep(3)
2025-11-28 16:02:13 +08:00
count = count_fingerprint_browsers()
if count > limit:
2025-11-30 00:19:46 +08:00
logger.warning(f"指纹浏览器数量 {count} 超过限制 {limit},开始删除超出部分")
delete_excess_browsers(limit)
2025-11-28 16:02:13 +08:00
def main():
"""
2025-11-30 00:19:46 +08:00
多线程并发管理按代理数量并发创建指纹浏览器并执行任务 3 秒监控数量超限则从末尾删除多余浏览器
2025-11-28 16:02:13 +08:00
"""
proxies = list(proxy_list)
2025-11-21 12:41:26 +08:00
while True:
2025-11-28 16:02:13 +08:00
stop_event = threading.Event()
restart_event = threading.Event()
if is_forbidden_time():
2025-11-30 09:27:15 +08:00
if stop_event.wait(timeout=60):
2025-11-30 00:19:46 +08:00
continue
2025-11-28 16:02:13 +08:00
cleanup_all_browsers()
2025-11-30 09:15:08 +08:00
logger.info("处于禁跑时段,等待至禁跑结束")
wait_until_out_of_forbidden()
2025-11-28 09:17:59 +08:00
continue
2025-11-28 16:02:13 +08:00
2025-11-29 11:46:09 +08:00
executor = ThreadPoolExecutor(max_workers=len(proxies))
try:
2025-11-28 16:02:13 +08:00
futures_map = {executor.submit(proxy_loop, p, stop_event): p for p in proxies}
monitor_thread = threading.Thread(
target=monitor_browsers_and_restart,
args=(len(proxies), stop_event, restart_event),
daemon=True,
)
monitor_thread.start()
while True:
if restart_event.is_set():
stop_event.set()
try:
2025-11-30 00:30:56 +08:00
executor.shutdown(wait=True)
2025-11-28 16:02:13 +08:00
except Exception:
pass
break
if is_forbidden_time():
2025-11-30 09:21:14 +08:00
logger.info("进入禁跑时段停止当前批次等待1分钟后清理指纹浏览器")
2025-11-28 16:02:13 +08:00
stop_event.set()
try:
2025-11-30 00:30:56 +08:00
executor.shutdown(wait=True)
2025-11-28 16:02:13 +08:00
except Exception:
pass
2025-11-30 09:21:14 +08:00
time.sleep(60)
2025-11-28 16:02:13 +08:00
cleanup_all_browsers()
2025-11-30 09:15:08 +08:00
wait_until_out_of_forbidden()
2025-11-28 16:02:13 +08:00
break
2025-11-28 16:05:11 +08:00
for f, proxy in list(futures_map.items()):
if f.done() and not stop_event.is_set() and not restart_event.is_set():
try:
_ = f.exception()
except Exception:
pass
try:
new_future = executor.submit(proxy_loop, proxy, stop_event)
del futures_map[f]
futures_map[new_future] = proxy
except Exception as e:
logger.error(f"重启代理线程失败: {proxy} - {e}")
2025-11-28 16:02:13 +08:00
time.sleep(0.2)
try:
monitor_thread.join(timeout=5)
except Exception:
pass
2025-11-29 11:46:09 +08:00
finally:
try:
2025-11-30 00:30:56 +08:00
executor.shutdown(wait=True)
2025-11-29 11:46:09 +08:00
except Exception:
pass
2025-11-28 16:02:13 +08:00
continue
2025-11-21 12:41:26 +08:00
2025-11-20 11:42:18 +08:00
if __name__ == "__main__":
2025-11-28 16:02:13 +08:00
main()