cnks 0.3.1__py3-none-any.whl → 0.3.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
src/searcher.py CHANGED
@@ -13,53 +13,42 @@
13
13
  5. 返回链接列表给工作者
14
14
  """
15
15
 
16
- import logging
17
16
  import os
18
- import traceback
19
17
  import asyncio
20
18
  import platform
21
19
  import time
22
20
  from typing import List, Dict, Any, Optional
23
21
 
24
- # 配置日志记录
25
- try:
26
- # 尝试使用绝对路径
27
- log_dir = os.path.dirname(os.path.abspath(__file__))
28
- log_file = os.path.join(os.path.dirname(log_dir), "cnks_searcher.log")
22
+ # 禁用日志记录
23
+ class DummyLogger:
24
+ """空日志记录器,用于禁用日志输出"""
25
+ def __init__(self, *args, **kwargs):
26
+ pass
27
+
28
+ def debug(self, *args, **kwargs):
29
+ pass
29
30
 
30
- # 创建处理器
31
- file_handler = logging.FileHandler(log_file, mode="a")
32
- console_handler = logging.StreamHandler()
31
+ def info(self, *args, **kwargs):
32
+ pass
33
33
 
34
- # 设置格式
35
- formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
36
- file_handler.setFormatter(formatter)
37
- console_handler.setFormatter(formatter)
34
+ def warning(self, *args, **kwargs):
35
+ pass
38
36
 
39
- # 获取日志记录器并添加处理器
40
- logger = logging.getLogger("cnks.searcher")
41
- logger.setLevel(logging.DEBUG)
37
+ def error(self, *args, **kwargs):
38
+ pass
42
39
 
43
- # 移除现有处理器以避免重复
44
- if logger.handlers:
45
- for handler in logger.handlers:
46
- logger.removeHandler(handler)
40
+ def critical(self, *args, **kwargs):
41
+ pass
47
42
 
48
- logger.addHandler(file_handler)
49
- logger.addHandler(console_handler)
43
+ def addHandler(self, *args, **kwargs):
44
+ pass
50
45
 
51
- # 打印确认信息
52
- print(f"Searcher logger initialized, logging to: {log_file}")
53
- logger.info(f"Searcher logging to: {log_file}")
54
- except Exception as e:
55
- # 回退到基本控制台日志记录
56
- logging.basicConfig(
57
- level=logging.DEBUG,
58
- format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
59
- )
60
- logger = logging.getLogger("cnks.searcher")
61
- logger.error(f"Failed to set up file logging: {str(e)}")
62
- print(f"Error setting up searcher file logging: {str(e)}")
46
+ def setLevel(self, *args, **kwargs):
47
+ pass
48
+
49
+ # 使用空日志记录器
50
+ logger = DummyLogger()
51
+ print = lambda *args, **kwargs: None # 禁用print函数
63
52
 
64
53
  # 导入必要的模块
65
54
  try:
@@ -67,7 +56,6 @@ try:
67
56
  PLAYWRIGHT_AVAILABLE = True
68
57
  except ImportError:
69
58
  PLAYWRIGHT_AVAILABLE = False
70
- logger.error("Playwright not available. Install with: pip install playwright")
71
59
 
72
60
  # 尝试导入其他模块
73
61
  try:
@@ -80,7 +68,7 @@ except ImportError:
80
68
  from click50 import set_results_per_page
81
69
  from extractlink import extract_links_from_page
82
70
  except ImportError:
83
- logger.warning("无法导入验证处理、过滤和链接提取模块,部分功能可能不可用")
71
+ pass
84
72
 
85
73
  # 默认搜索URL
86
74
  SEARCH_URL = os.environ.get("SEARCH_URL", "https://kns.cnki.net/kns8s/search")
@@ -166,11 +154,8 @@ class Searcher:
166
154
  self.context = None
167
155
  self.browser_started = False
168
156
 
169
- # 创建调试截图目录
170
- self.debug_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "debug_screenshots")
171
- os.makedirs(self.debug_dir, exist_ok=True)
172
-
173
- logger.info("Searcher初始化完成")
157
+ # 不创建调试截图目录
158
+ self.debug_dir = "/dev/null" if platform.system() != "Windows" else "NUL"
174
159
 
175
160
  async def start_browser(self) -> bool:
176
161
  """
@@ -180,24 +165,18 @@ class Searcher:
180
165
  bool: 浏览器是否成功启动
181
166
  """
182
167
  if self.browser_started:
183
- logger.info("浏览器已启动,重用现有实例")
184
168
  return True
185
169
 
186
170
  if not PLAYWRIGHT_AVAILABLE:
187
- logger.error("Playwright未安装,无法启动浏览器")
188
171
  return False
189
172
 
190
173
  try:
191
- logger.info("使用持久上下文启动浏览器")
192
-
193
174
  # 创建Playwright实例
194
175
  self.playwright = await async_playwright().start()
195
- logger.info("Playwright已启动")
196
176
 
197
177
  # 创建用户数据目录(如果不存在)
198
178
  user_data_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "chrome_data")
199
179
  os.makedirs(user_data_dir, exist_ok=True)
200
- logger.info(f"使用Chrome用户数据目录: {user_data_dir}")
201
180
 
202
181
  # 设置Chrome参数
203
182
  browser_args = [
@@ -207,8 +186,6 @@ class Searcher:
207
186
 
208
187
  # 查找Chrome可执行文件
209
188
  chrome_path = self._find_chrome_executable()
210
- if chrome_path:
211
- logger.info(f"使用Chrome路径: {chrome_path}")
212
189
 
213
190
  # 使用持久上下文启动浏览器
214
191
  self.context = await self.playwright.chromium.launch_persistent_context(
@@ -217,22 +194,17 @@ class Searcher:
217
194
  headless=False,
218
195
  args=browser_args
219
196
  )
220
- logger.info("使用持久上下文启动浏览器成功")
221
197
 
222
198
  # 创建一个初始页面确保上下文已激活
223
199
  init_page = await self.context.new_page()
224
200
  await init_page.goto("about:blank")
225
201
  await init_page.close()
226
- logger.info("使用空白页初始化浏览器")
227
202
 
228
203
  # 标记浏览器已启动
229
204
  self.browser_started = True
230
205
  return True
231
206
 
232
- except Exception as e:
233
- logger.error(f"启动浏览器时出错: {str(e)}")
234
- logger.error(traceback.format_exc())
235
-
207
+ except Exception:
236
208
  # 清理资源
237
209
  await self.close_browser()
238
210
  return False
@@ -244,29 +216,22 @@ class Searcher:
244
216
  Returns:
245
217
  bool: 是否成功关闭
246
218
  """
247
- logger.info("关闭浏览器资源")
248
-
249
219
  try:
250
220
  # 关闭浏览器上下文
251
221
  if self.context:
252
222
  await self.context.close()
253
223
  self.context = None
254
- logger.info("浏览器上下文已关闭")
255
224
 
256
225
  # 停止Playwright
257
226
  if self.playwright:
258
227
  await self.playwright.stop()
259
228
  self.playwright = None
260
- logger.info("Playwright已停止")
261
229
 
262
230
  # 重置浏览器状态
263
231
  self.browser_started = False
264
- logger.info("浏览器资源已成功关闭")
265
232
  return True
266
233
 
267
- except Exception as e:
268
- logger.error(f"关闭浏览器时出错: {str(e)}")
269
- logger.error(traceback.format_exc())
234
+ except Exception:
270
235
  self.browser_started = False
271
236
  return False
272
237
 
@@ -303,10 +268,8 @@ class Searcher:
303
268
  for path in candidates:
304
269
  expanded_path = os.path.expanduser(path)
305
270
  if os.path.exists(expanded_path) and os.access(expanded_path, os.X_OK):
306
- logger.info(f"找到Chrome路径: {expanded_path}")
307
271
  return expanded_path
308
272
 
309
- logger.warning("未找到Chrome可执行文件")
310
273
  return None
311
274
 
312
275
  async def open_page(self, url: str) -> Optional[Page]:
@@ -322,13 +285,11 @@ class Searcher:
322
285
  if not self.browser_started:
323
286
  success = await self.start_browser()
324
287
  if not success:
325
- logger.error("无法启动浏览器,放弃打开页面")
326
288
  return None
327
289
 
328
290
  try:
329
291
  # 创建新标签页
330
292
  page = await self.context.new_page()
331
- logger.info(f"已创建新标签页,正在导航到URL: {url}")
332
293
 
333
294
  # 导航到指定URL
334
295
  await page.goto(url, wait_until="domcontentloaded", timeout=30000)
@@ -336,19 +297,14 @@ class Searcher:
336
297
  # 检查是否需要验证
337
298
  verification_needed = await check_verification_needed(page)
338
299
  if verification_needed:
339
- logger.info("检测到需要人工验证,等待10秒钟...")
340
300
  await page.wait_for_timeout(10000) # 等待10秒
341
- else:
342
- logger.info("未检测到验证页面,继续执行")
343
301
 
344
302
  # 等待页面完全加载
345
303
  await page.wait_for_load_state("networkidle", timeout=30000)
346
304
 
347
305
  return page
348
306
 
349
- except Exception as e:
350
- logger.error(f"打开页面时出错: {str(e)}")
351
- logger.error(traceback.format_exc())
307
+ except Exception:
352
308
  return None
353
309
 
354
310
  async def apply_cssci_filter(self, page) -> Dict[str, Any]:
@@ -361,7 +317,6 @@ class Searcher:
361
317
  Returns:
362
318
  Dict: 包含操作结果的字典,包括是否成功、消息等
363
319
  """
364
- logger.info("开始应用CSSCI筛选")
365
320
  result = {
366
321
  "success": True, # 默认为True,即使没找到也算成功(跳过继续处理)
367
322
  "message": "",
@@ -378,7 +333,6 @@ class Searcher:
378
333
  """)
379
334
 
380
335
  if cssci_checked:
381
- logger.info("CSSCI选项已被选中,现在点击应用按钮")
382
336
  # 即使已选中也点击应用按钮确保筛选生效
383
337
  apply_result = await self.click_apply_button(page)
384
338
  result["filter_applied"] = True
@@ -390,7 +344,6 @@ class Searcher:
390
344
 
391
345
  if cssci_checkbox:
392
346
  # 使用模拟真实鼠标点击的方法点击CSSCI复选框
393
- logger.info("找到CSSCI复选框,使用真实鼠标点击模拟")
394
347
 
395
348
  # 获取元素的位置
396
349
  bbox = await cssci_checkbox.bounding_box()
@@ -407,18 +360,14 @@ class Searcher:
407
360
  await asyncio.sleep(0.05)
408
361
  # 释放鼠标按钮
409
362
  await page.mouse.up()
410
-
411
- logger.info("成功模拟真实鼠标点击CSSCI复选框")
412
363
  else:
413
364
  # 如果无法获取位置,回退到普通点击
414
- logger.warning("无法获取CSSCI复选框位置,使用普通点击")
415
365
  await cssci_checkbox.click()
416
366
 
417
367
  # 等待一秒确保复选框状态更新
418
368
  await asyncio.sleep(1)
419
369
 
420
370
  # 点击应用按钮
421
- logger.info("点击应用按钮应用筛选")
422
371
  apply_result = await self.click_apply_button(page)
423
372
 
424
373
  # 等待页面刷新
@@ -430,17 +379,13 @@ class Searcher:
430
379
 
431
380
  # 如果通过精确选择器未找到,则尝试查找来源类别区域
432
381
  source_category = await page.query_selector('.source-category, .filter-item:has-text("来源类别")')
433
- logger.debug("[DEBUG] 尝试查找来源类别区域")
434
382
 
435
383
  if source_category:
436
- logger.debug("[DEBUG] 找到了来源类别区域")
437
-
438
384
  # 在来源类别区域内查找CSSCI选项
439
385
  cssci_checkbox = await source_category.query_selector('input[type="checkbox"]:near(:text("CSSCI"))')
440
386
 
441
387
  if cssci_checkbox:
442
388
  # 使用模拟真实鼠标点击的方法点击CSSCI复选框
443
- logger.info("找到CSSCI复选框(在来源类别区域内),使用真实鼠标点击模拟")
444
389
 
445
390
  # 获取元素的位置
446
391
  bbox = await cssci_checkbox.bounding_box()
@@ -457,18 +402,14 @@ class Searcher:
457
402
  await asyncio.sleep(0.05)
458
403
  # 释放鼠标按钮
459
404
  await page.mouse.up()
460
-
461
- logger.info("成功模拟真实鼠标点击CSSCI复选框")
462
405
  else:
463
406
  # 如果无法获取位置,回退到普通点击
464
- logger.warning("无法获取CSSCI复选框位置,使用普通点击")
465
407
  await cssci_checkbox.click()
466
408
 
467
409
  # 等待一秒确保复选框状态更新
468
410
  await asyncio.sleep(1)
469
411
 
470
412
  # 点击应用按钮
471
- logger.info("点击应用按钮应用筛选")
472
413
  apply_result = await self.click_apply_button(page)
473
414
 
474
415
  # 等待页面刷新
@@ -478,13 +419,10 @@ class Searcher:
478
419
  result["message"] = f"成功勾选CSSCI选项并点击应用按钮,结果: {apply_result}"
479
420
  return result
480
421
  else:
481
- logger.debug("[DEBUG] 在来源类别区域未找到CSSCI选项")
482
-
483
422
  # 尝试另一种方式:直接在整个页面中查找CSSCI
484
423
  cssci_text = await page.query_selector(':text("CSSCI")')
485
424
  if cssci_text:
486
425
  # 使用模拟真实鼠标点击的方法点击CSSCI文本
487
- logger.info("找到CSSCI文本,使用真实鼠标点击模拟")
488
426
 
489
427
  # 获取元素的位置
490
428
  bbox = await cssci_text.bounding_box()
@@ -501,18 +439,14 @@ class Searcher:
501
439
  await asyncio.sleep(0.05)
502
440
  # 释放鼠标按钮
503
441
  await page.mouse.up()
504
-
505
- logger.info("成功模拟真实鼠标点击CSSCI文本")
506
442
  else:
507
443
  # 如果无法获取位置,回退到普通点击
508
- logger.warning("无法获取CSSCI文本位置,使用普通点击")
509
444
  await cssci_text.click()
510
445
 
511
446
  # 等待一秒确保复选框状态更新
512
447
  await asyncio.sleep(1)
513
448
 
514
449
  # 点击应用按钮
515
- logger.info("点击应用按钮应用筛选")
516
450
  apply_result = await self.click_apply_button(page)
517
451
 
518
452
  await page.wait_for_load_state("networkidle")
@@ -524,14 +458,10 @@ class Searcher:
524
458
  result["message"] = "未找到CSSCI选项"
525
459
  return result
526
460
  else:
527
- logger.debug("[DEBUG] 未找到来源类别区域")
528
461
  result["message"] = "未找到来源类别区域和CSSCI选项"
529
462
  return result
530
463
 
531
464
  except Exception as e:
532
- logger.error(f"应用CSSCI筛选时发生错误: {str(e)}")
533
- logger.error(traceback.format_exc())
534
-
535
465
  result["message"] = f"应用CSSCI筛选时发生错误: {str(e)}"
536
466
  return result
537
467
 
@@ -560,8 +490,6 @@ class Searcher:
560
490
  try:
561
491
  button = await page.query_selector(btn_selector)
562
492
  if button:
563
- logger.info(f"找到应用按钮: {btn_selector},使用真实鼠标点击模拟")
564
-
565
493
  # 获取按钮位置
566
494
  bbox = await button.bounding_box()
567
495
  if bbox:
@@ -577,20 +505,16 @@ class Searcher:
577
505
  await asyncio.sleep(0.05)
578
506
  # 释放鼠标按钮
579
507
  await page.mouse.up()
580
-
581
- logger.info(f"成功模拟真实鼠标点击应用按钮: {btn_selector}")
582
508
  else:
583
509
  # 如果无法获取位置,回退到普通点击
584
- logger.warning(f"无法获取按钮 '{btn_selector}' 位置,使用普通点击")
585
510
  await button.click()
586
511
 
587
512
  await page.wait_for_load_state("networkidle", timeout=10000)
588
513
  return True
589
- except Exception as e:
590
- logger.warning(f"点击按钮 '{btn_selector}' 失败: {str(e)}")
514
+ except Exception:
515
+ pass
591
516
 
592
517
  # 如果没有找到标准按钮,尝试通过JavaScript应用筛选
593
- logger.info("尝试通过JavaScript应用筛选")
594
518
  apply_js = """
595
519
  () => {
596
520
  try {
@@ -630,11 +554,9 @@ class Searcher:
630
554
 
631
555
  apply_result = await page.evaluate(apply_js)
632
556
  if apply_result.get('clicked', False):
633
- logger.info(f"通过JavaScript应用筛选成功: {apply_result}")
634
557
  await page.wait_for_load_state("networkidle", timeout=10000)
635
558
  return True
636
559
 
637
- logger.warning("未找到应用筛选的按钮")
638
560
  return False
639
561
 
640
562
  async def search_keyword(self, keyword: str) -> List[str]:
@@ -648,36 +570,28 @@ class Searcher:
648
570
  List[str]: 搜索结果链接列表
649
571
  """
650
572
  if not PLAYWRIGHT_AVAILABLE:
651
- error_msg = "Playwright未安装,无法执行搜索"
652
- logger.error(error_msg)
653
- raise RuntimeError(error_msg)
573
+ return []
654
574
 
655
575
  # 确保浏览器已启动
656
576
  if not self.browser_started:
657
577
  success = await self.start_browser()
658
578
  if not success:
659
- logger.error("无法启动浏览器,放弃搜索")
660
579
  return []
661
580
 
662
581
  page = None
663
582
  try:
664
- logger.info(f"搜索关键词: {keyword}")
665
-
666
583
  # 使用open_page方法打开搜索页面
667
584
  page = await self.open_page(SEARCH_URL)
668
585
  if not page:
669
- logger.error("无法打开搜索页面")
670
586
  return []
671
587
 
672
588
  # 输入搜索词
673
- logger.info("正在输入搜索关键词")
674
589
  search_input = await page.query_selector('#txt_search-input')
675
590
 
676
591
  if search_input:
677
592
  await search_input.fill(keyword)
678
593
  await search_input.press('Enter')
679
594
  else:
680
- logger.warning("未找到搜索输入框")
681
595
  # 尝试其他可能的搜索输入框选择器
682
596
  alternate_selectors = [
683
597
  'input[type="search"]',
@@ -688,7 +602,6 @@ class Searcher:
688
602
  try:
689
603
  input_field = await page.query_selector(selector)
690
604
  if input_field:
691
- logger.info(f"找到替代搜索输入框: {selector}")
692
605
  await input_field.fill(keyword)
693
606
  await input_field.press('Enter')
694
607
  break
@@ -696,72 +609,46 @@ class Searcher:
696
609
  continue
697
610
 
698
611
  # 等待搜索结果
699
- logger.info("等待搜索结果")
700
612
  await page.wait_for_load_state("networkidle", timeout=60000)
701
613
 
702
614
  # 尝试等待结果列表出现
703
615
  try:
704
- logger.info("等待结果列表出现")
705
616
  await page.wait_for_selector('.result-table-list', timeout=10000)
706
- logger.info("已找到结果列表")
707
- except Exception as e:
708
- logger.warning(f"未找到结果列表: {str(e)}")
617
+ except Exception:
618
+ pass
709
619
 
710
620
  # 设置每页显示50条结果
711
- logger.info("设置每页显示50条结果")
712
621
  try:
713
622
  # 调用click50模块的设置方法
714
623
  page_setting_result = await set_results_per_page(page)
715
624
  if page_setting_result.get("success", False):
716
625
  if page_setting_result.get("setting_applied", False):
717
- logger.info("成功设置每页显示50条结果")
718
626
  # 等待页面重新加载结果
719
627
  await page.wait_for_load_state("networkidle", timeout=10000)
720
628
  # 增加1000ms额外延时确保页面完全加载
721
- logger.info("增加1000ms延时以确保页面完全加载")
722
629
  await asyncio.sleep(1)
723
- else:
724
- logger.info(f"显示设置未应用: {page_setting_result.get('message', '')}")
725
- else:
726
- logger.warning(f"设置显示数量失败: {page_setting_result.get('message', '')}")
727
- except Exception as e:
728
- logger.warning(f"设置显示数量时出错: {str(e)}")
729
- logger.warning(traceback.format_exc())
630
+ except Exception:
631
+ pass
730
632
 
731
633
  # 应用CSSCI筛选
732
- logger.info("应用CSSCI筛选(如果可用)")
733
634
  try:
734
635
  # 调用内部的CSSCI筛选方法
735
- filter_result = await self.apply_cssci_filter(page)
736
- if filter_result.get("success", False):
737
- if filter_result.get("filter_applied", False):
738
- logger.info("CSSCI筛选成功应用")
739
- else:
740
- logger.info(f"CSSCI筛选未应用: {filter_result.get('message', '')}")
741
- else:
742
- logger.warning(f"应用CSSCI筛选失败: {filter_result.get('message', '')}")
743
- except Exception as e:
744
- logger.warning(f"CSSCI筛选时出错: {str(e)}")
745
- logger.warning(traceback.format_exc())
636
+ await self.apply_cssci_filter(page)
637
+ except Exception:
638
+ pass
746
639
 
747
640
  # 强制等待几秒,确保页面加载完毕
748
- logger.info("额外等待几秒确保结果加载完毕")
749
641
  await asyncio.sleep(5)
750
642
 
751
643
  # 提取链接 - 使用独立的extractlink模块
752
- logger.info("使用extractlink模块从搜索结果中提取链接")
753
644
  links = await extract_links_from_page(page)
754
- logger.info(f"提取到 {len(links)} 个链接")
755
645
 
756
646
  return links
757
647
 
758
- except Exception as e:
759
- logger.error(f"搜索关键词 {keyword} 时出错: {str(e)}")
760
- logger.error(traceback.format_exc())
648
+ except Exception:
761
649
  return []
762
650
 
763
651
  finally:
764
652
  # 关闭标签页但保持浏览器打开
765
653
  if page:
766
- await page.close()
767
- logger.info("已关闭搜索页面,保持浏览器打开")
654
+ await page.close()