ts
This commit is contained in:
+18
-36
@@ -556,31 +556,15 @@ def resolve_profile_by_search(
|
|||||||
xhs_no = ""
|
xhs_no = ""
|
||||||
try:
|
try:
|
||||||
full_text = (it.inner_text() or "").strip()
|
full_text = (it.inner_text() or "").strip()
|
||||||
_log(log, f"[调试] 原始文本: {full_text[:100]}")
|
# 提取小红书号
|
||||||
|
m = re.search(r'小红书号[::]\s*([A-Za-z0-9]+)', full_text)
|
||||||
# 首先提取小红书号(尝试多种模式)
|
if m:
|
||||||
patterns = [
|
xhs_no = m.group(1).strip()
|
||||||
r'小红书号[::]\s*([A-Za-z0-9]+)',
|
|
||||||
r'小红书号[::]\s*([A-Za-z0-9]+)',
|
|
||||||
r'ID[::]\s*([A-Za-z0-9]+)',
|
|
||||||
r'账号[::]\s*([A-Za-z0-9]+)',
|
|
||||||
]
|
|
||||||
for pattern in patterns:
|
|
||||||
m = re.search(pattern, full_text)
|
|
||||||
if m:
|
|
||||||
xhs_no = m.group(1).strip()
|
|
||||||
_log(log, f"[调试] 匹配到小红书号: {xhs_no} (pattern: {pattern})")
|
|
||||||
break
|
|
||||||
|
|
||||||
if not xhs_no:
|
|
||||||
_log(log, f"[调试] 未能匹配小红书号")
|
|
||||||
|
|
||||||
# 提取昵称:从开头去除干扰信息
|
# 提取昵称:从开头去除干扰信息
|
||||||
lines = full_text.split('\n')
|
lines = full_text.split('\n')
|
||||||
if lines:
|
if lines:
|
||||||
main_text = lines[0].strip()
|
main_text = lines[0].strip()
|
||||||
_log(log, f"[调试] 第一行文本: {main_text}")
|
|
||||||
|
|
||||||
# 移除小红书号及其后面的所有内容
|
# 移除小红书号及其后面的所有内容
|
||||||
main_text = re.sub(r'小红书号[::].*$', '', main_text).strip()
|
main_text = re.sub(r'小红书号[::].*$', '', main_text).strip()
|
||||||
# 移除时间信息
|
# 移除时间信息
|
||||||
@@ -592,11 +576,9 @@ def resolve_profile_by_search(
|
|||||||
# 移除分隔符
|
# 移除分隔符
|
||||||
main_text = re.sub(r'\s*[・·]\s*', ' ', main_text).strip()
|
main_text = re.sub(r'\s*[・·]\s*', ' ', main_text).strip()
|
||||||
nickname = re.sub(r'\s+', ' ', main_text).strip()
|
nickname = re.sub(r'\s+', ' ', main_text).strip()
|
||||||
_log(log, f"[调试] 清理后昵称: {nickname}")
|
|
||||||
else:
|
else:
|
||||||
nickname = full_text
|
nickname = full_text
|
||||||
except Exception as e:
|
except Exception:
|
||||||
_log(log, f"[调试] 解析异常: {e}")
|
|
||||||
nickname = ""
|
nickname = ""
|
||||||
xhs_no = ""
|
xhs_no = ""
|
||||||
else:
|
else:
|
||||||
@@ -777,12 +759,12 @@ def crawl_detail(
|
|||||||
slow_mo: int = 80,
|
slow_mo: int = 80,
|
||||||
batch_min: int = 3,
|
batch_min: int = 3,
|
||||||
batch_max: int = 7,
|
batch_max: int = 7,
|
||||||
open_wait: Tuple[int, int] = (1800, 3200),
|
open_wait: Tuple[int, int] = (1200, 2000), # 风控安全:适度等待
|
||||||
between_wait: Tuple[int, int] = (1500, 3200),
|
between_wait: Tuple[int, int] = (1000, 1800), # 风控安全:笔记间等待
|
||||||
scroll_wait: Tuple[int, int] = (1600, 2800),
|
scroll_wait: Tuple[int, int] = (1000, 1800), # 风控安全:滚动等待
|
||||||
long_break: Tuple[int, int] = (6000, 12000),
|
long_break: Tuple[int, int] = (5000, 9000), # 风控安全:长休息时间
|
||||||
scroll_target_new: int = 2,
|
scroll_target_new: int = 2,
|
||||||
scroll_max_attempts: int = 10,
|
scroll_max_attempts: int = 8, # 风控安全:适度滚动次数
|
||||||
out_file: str = "out/result_modal_details.json",
|
out_file: str = "out/result_modal_details.json",
|
||||||
stop_event=None,
|
stop_event=None,
|
||||||
log: Optional[Callable[[str], None]] = None,
|
log: Optional[Callable[[str], None]] = None,
|
||||||
@@ -955,7 +937,7 @@ def crawl_detail(
|
|||||||
""")
|
""")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
page.wait_for_timeout(random.randint(2000, 3500))
|
page.wait_for_timeout(random.randint(1500, 2500)) # 风控安全:适度等待
|
||||||
after2 = count_visible_unprocessed(page, processed)
|
after2 = count_visible_unprocessed(page, processed)
|
||||||
if after2 > before:
|
if after2 > before:
|
||||||
return True
|
return True
|
||||||
@@ -994,8 +976,8 @@ def crawl_detail(
|
|||||||
page = context.new_page()
|
page = context.new_page()
|
||||||
|
|
||||||
page.goto(profile_url, wait_until="domcontentloaded", timeout=60000)
|
page.goto(profile_url, wait_until="domcontentloaded", timeout=60000)
|
||||||
page.wait_for_selector(NOTE_CONTAINER_SELECTOR, timeout=30000)
|
page.wait_for_selector(NOTE_CONTAINER_SELECTOR, timeout=25000) # 风控安全:适度等待
|
||||||
_wait_ms(page, (1200, 2200))
|
_wait_ms(page, (800, 1500)) # 风控安全:适度等待
|
||||||
|
|
||||||
safety_round = 0
|
safety_round = 0
|
||||||
while len(results) < limit and safety_round < 2000:
|
while len(results) < limit and safety_round < 2000:
|
||||||
@@ -1020,7 +1002,7 @@ def crawl_detail(
|
|||||||
item.scroll_into_view_if_needed()
|
item.scroll_into_view_if_needed()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
_wait_ms(page, (700, 1300))
|
_wait_ms(page, (500, 900)) # 风控安全:适度等待
|
||||||
|
|
||||||
try:
|
try:
|
||||||
opener.click(timeout=6000)
|
opener.click(timeout=6000)
|
||||||
@@ -1031,14 +1013,14 @@ def crawl_detail(
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 关键:不要用 #noteContainer 这种“永远可见”的元素判断弹框是否打开
|
# 关键:不要用 #noteContainer 这种"永远可见"的元素判断弹框是否打开
|
||||||
page.wait_for_selector(DETAIL_TITLE_SELECTOR, state="visible", timeout=25000)
|
page.wait_for_selector(DETAIL_TITLE_SELECTOR, state="visible", timeout=15000) # 优化:从 25 秒降至 15 秒
|
||||||
except PWTimeoutError:
|
except PWTimeoutError:
|
||||||
# 先做一次“强制点击+更长等待”的快速重试,尽量别直接跳过
|
# 先做一次"强制点击+更长等待"的快速重试,尽量别直接跳过
|
||||||
retry_ok = False
|
retry_ok = False
|
||||||
try:
|
try:
|
||||||
opener.click(timeout=6000, force=True)
|
opener.click(timeout=6000, force=True)
|
||||||
page.wait_for_selector(DETAIL_TITLE_SELECTOR, state="visible", timeout=30000)
|
page.wait_for_selector(DETAIL_TITLE_SELECTOR, state="visible", timeout=15000) # 优化:从 30 秒降至 15 秒
|
||||||
retry_ok = True
|
retry_ok = True
|
||||||
except Exception:
|
except Exception:
|
||||||
retry_ok = False
|
retry_ok = False
|
||||||
|
|||||||
Reference in New Issue
Block a user