This commit is contained in:
van
2026-04-26 13:55:54 +08:00
commit 83c48dbed9
24 changed files with 1955 additions and 0 deletions

479
.py Normal file
View File

@@ -0,0 +1,479 @@
import time
import json
import re
import os
import platform
from DrissionPage import ChromiumPage, ChromiumOptions
# Ubuntu 上常见的 Chrome/Chromium 路径
UBUNTU_CHROME_PATHS = [
'/usr/bin/google-chrome',
'/usr/bin/google-chrome-stable',
'/usr/bin/chromium-browser',
'/usr/bin/chromium',
'/snap/bin/chromium',
'/opt/google/chrome/chrome',
]
# 是否使用无头模式headless
# True: 无界面模式,适合服务器环境
# False: 有界面模式,需要 X11 或 Wayland
USE_HEADLESS = True # 可以根据需要修改
# 全局浏览器实例
global_page = None
def find_chrome_path():
"""自动查找 Ubuntu 系统中的 Chrome/Chromium 路径"""
print("正在查找 Chrome/Chromium 浏览器...")
# 首先尝试常见的路径
for path in UBUNTU_CHROME_PATHS:
if os.path.exists(path):
print(f"✅ 找到浏览器: {path}")
return path
# 尝试使用 which 命令查找
import subprocess
try:
result = subprocess.run(['which', 'google-chrome'],
capture_output=True, text=True, timeout=5)
if result.returncode == 0 and os.path.exists(result.stdout.strip()):
path = result.stdout.strip()
print(f"✅ 通过 which 找到浏览器: {path}")
return path
except:
pass
try:
result = subprocess.run(['which', 'chromium-browser'],
capture_output=True, text=True, timeout=5)
if result.returncode == 0 and os.path.exists(result.stdout.strip()):
path = result.stdout.strip()
print(f"✅ 通过 which 找到浏览器: {path}")
return path
except:
pass
# 如果都找不到,返回最常见的路径
default_path = '/usr/bin/google-chrome'
print(f"⚠️ 未找到浏览器,将使用默认路径: {default_path}")
print("请确保已安装 Google Chrome 或 Chromium:")
print(" sudo apt update")
print(" sudo apt install -y google-chrome-stable")
print(" 或者")
print(" sudo apt install -y chromium-browser")
return default_path
def get_global_browser():
"""获取全局浏览器实例Ubuntu 版本)"""
global global_page
if global_page is None:
print("="*60)
print("Ubuntu 浏览器初始化")
print("="*60)
# 检查操作系统
if platform.system() != 'Linux':
print(f"⚠️ 警告: 当前系统是 {platform.system()},此脚本专为 Ubuntu 设计")
# 查找 Chrome 路径
chrome_path = find_chrome_path()
options = ChromiumOptions()
options.set_browser_path(chrome_path)
# Ubuntu 服务器环境通常使用无头模式
if USE_HEADLESS:
print("配置为无头模式headless...")
try:
options.headless(True)
except:
# 如果 headless 方法不存在,使用参数
try:
options.set_argument('--headless=new')
options.set_argument('--no-sandbox')
options.set_argument('--disable-dev-shm-usage')
except:
pass
else:
print("配置为有界面模式...")
# 检查是否有显示环境
display = os.environ.get('DISPLAY')
if not display:
print("⚠️ 警告: 未检测到 DISPLAY 环境变量")
print("如果无法显示浏览器,请:")
print(" 1. 设置 USE_HEADLESS = True")
print(" 2. 或者设置 DISPLAY 环境变量(如 DISPLAY=:0")
print(" 3. 或者使用 Xvfb虚拟显示")
# Linux 特定参数
try:
options.set_argument('--no-sandbox') # 在某些环境下需要
options.set_argument('--disable-dev-shm-usage') # 避免 /dev/shm 空间不足
options.set_argument('--disable-gpu') # 禁用 GPU可选在 headless 模式下有用)
except:
pass
print(f"正在启动浏览器...")
print(f"浏览器路径: {chrome_path}")
if USE_HEADLESS:
print("模式: 无头模式(后台运行)")
else:
print("模式: 有界面模式")
try:
global_page = ChromiumPage(options)
print("✅ 浏览器已成功启动!")
time.sleep(2) # 等待浏览器完全启动
except Exception as e:
print(f"❌ 浏览器启动失败: {e}")
print("\n可能的解决方案:")
print("1. 确保已安装 Chrome/Chromium:")
print(" sudo apt update")
print(" sudo apt install -y google-chrome-stable")
print("2. 如果使用无头模式失败,尝试设置 USE_HEADLESS = False")
print("3. 确保有足够的权限")
print("4. 检查是否缺少依赖:")
print(" sudo apt install -y libnss3 libatk-bridge2.0-0 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libasound2")
import traceback
traceback.print_exc()
raise
else:
print("使用已存在的浏览器实例")
return global_page
def extract_logistics_info(tracking_url):
"""
从京东物流追踪页面提取运单号、承运人等信息Ubuntu 版本)
Args:
tracking_url: 物流追踪页面 URL例如 https://3.cn/2t-Iibig
Returns:
dict: 包含运单号、承运人、承运人电话、物流跟踪信息等的字典
"""
page = get_global_browser()
try:
print(f"\n正在打开物流追踪页面: {tracking_url}")
page.get(tracking_url)
print("页面加载中,请稍候...")
time.sleep(5) # 等待页面加载
# 检查页面是否成功加载
current_url = page.url
print(f"当前页面 URL: {current_url}")
# 检查页面标题
try:
title = page.title
print(f"页面标题: {title}")
except:
print("无法获取页面标题")
# 检查页面是否有内容
try:
html_length = len(page.html)
print(f"页面 HTML 长度: {html_length} 字符")
if html_length < 100:
print("⚠️ 警告: 页面内容可能未完全加载")
except Exception as e:
print(f"⚠️ 无法获取页面 HTML: {e}")
result = {
"waybill_no": None, # 运单号
"carrier": None, # 国内承运人
"carrier_phone": None, # 国内承运人电话
"tracking_info": [], # 物流跟踪信息列表
"raw_html": None # 原始 HTML用于调试
}
# 方法1: 监听网络请求,查找物流数据 API
print("\n方法1: 监听网络请求...")
page.listen.start()
# 滚动页面触发可能的请求
page.scroll.down(300)
time.sleep(2)
page.scroll.to_bottom()
time.sleep(3)
# 检查监听到的请求
responses = page.listen.get()
print(f"监听到 {len(responses)} 个请求")
# 查找可能的物流数据接口
possible_urls = [
'track', 'logistics', 'waybill', 'express',
'delivery', '3.cn', 'jd.com/logistics',
'api.m.jd.com', 'mapi.jd.com'
]
for resp in responses:
url = resp.url if hasattr(resp, 'url') else ''
url_lower = url.lower()
# 检查是否可能是物流相关的 API
if any(keyword in url_lower for keyword in possible_urls):
print(f"发现可能的物流 API: {url[:100]}")
try:
if hasattr(resp, 'response') and hasattr(resp.response, 'body'):
body = resp.response.body
# 处理 JSON 响应
if isinstance(body, dict):
json_data = body
elif isinstance(body, str):
try:
json_data = json.loads(body)
except:
continue
else:
continue
# 尝试从 JSON 中提取运单号等信息
extracted = extract_from_json(json_data)
if extracted:
result.update(extracted)
print("成功从 API 响应中提取数据")
return result
except Exception as e:
print(f"解析 API 响应时出错: {e}")
# 方法2: 从页面 HTML/DOM 中提取
print("\n方法2: 从页面 DOM 提取数据...")
html = page.html
result['raw_html'] = html[:5000] # 保存部分 HTML 用于调试
# 从 HTML 文本中提取运单号
waybill_patterns = [
r'运单号[:\s]*(\d+)',
r'waybill[_\s]*no["\']?\s*[:]\s*["\']?(\d+)',
r'tracking[_\s]*number["\']?\s*[:]\s*["\']?(\d+)',
r'"waybillNo"\s*[:]\s*["\']?(\d+)',
r'"trackingNumber"\s*[:]\s*["\']?(\d+)',
]
for pattern in waybill_patterns:
matches = re.findall(pattern, html, re.IGNORECASE)
if matches:
result['waybill_no'] = matches[0]
print(f"找到运单号: {result['waybill_no']}")
break
# 提取承运人
carrier_patterns = [
r'国内承运人[:\s]*([^\s<,]+)',
r'carrier[:\s]*([^\s<,]+)',
r'"carrier"\s*[:]\s*["\']?([^"\']+)',
]
for pattern in carrier_patterns:
matches = re.findall(pattern, html, re.IGNORECASE)
if matches:
result['carrier'] = matches[0].strip()
print(f"找到承运人: {result['carrier']}")
break
# 提取承运人电话
phone_patterns = [
r'国内承运人电话[:\s]*(\d+)',
r'carrier[_\s]*phone[:\s]*(\d+)',
r'"carrierPhone"\s*[:]\s*["\']?(\d+)',
]
for pattern in phone_patterns:
matches = re.findall(pattern, html, re.IGNORECASE)
if matches:
result['carrier_phone'] = matches[0]
print(f"找到承运人电话: {result['carrier_phone']}")
break
# 方法3: 从 DOM 元素中提取
print("\n方法3: 从 DOM 元素提取数据...")
# 尝试查找运单号元素
waybill_elements = page.eles('xpath=//*[contains(text(), "运单号") or contains(text(), "运单")]')
for elem in waybill_elements:
text = elem.text
parent_text = elem.parent().text if elem.parent() else ""
full_text = text + " " + parent_text
# 从文本中提取数字作为运单号
numbers = re.findall(r'\d{8,}', full_text)
if numbers and not result['waybill_no']:
result['waybill_no'] = numbers[0]
print(f"从元素文本中找到运单号: {result['waybill_no']}")
# 提取承运人
if '承运人' in text and not result['carrier']:
carrier_match = re.search(r'承运人[:\s]*([^\s<,]+)', full_text)
if carrier_match:
result['carrier'] = carrier_match.group(1).strip()
print(f"从元素文本中找到承运人: {result['carrier']}")
# 提取电话
if '电话' in text and not result['carrier_phone']:
phone_match = re.search(r'电话[:\s]*(\d+)', full_text)
if phone_match:
result['carrier_phone'] = phone_match.group(1)
print(f"从元素文本中找到电话: {result['carrier_phone']}")
# 提取物流跟踪信息(时间线)
print("\n提取物流跟踪信息...")
tracking_elements = page.eles('xpath=//*[contains(@class, "track") or contains(@class, "logistics") or contains(@class, "timeline")]')
if not tracking_elements:
# 尝试查找包含时间戳的元素
tracking_elements = page.eles('xpath=//*[contains(text(), "2025") or contains(text(), "货物") or contains(text(), "到达")]')
tracking_info = []
for elem in tracking_elements[:20]: # 限制数量
text = elem.text
if text and len(text) > 5:
# 尝试提取时间戳
time_match = re.search(r'(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})', text)
if time_match or any(keyword in text for keyword in ['货物', '到达', '揽收', '运输', '配送', '签收']):
tracking_info.append({
'text': text.strip(),
'time': time_match.group(1) if time_match else None
})
result['tracking_info'] = tracking_info[:10] # 最多保存10条
return result
except Exception as e:
print(f"提取物流信息时出错: {e}")
import traceback
traceback.print_exc()
return None
def extract_from_json(json_data):
"""
从 JSON 数据中提取物流信息
Args:
json_data: JSON 字典
Returns:
dict: 提取到的物流信息
"""
result = {}
def search_dict(d, key_patterns):
"""递归搜索字典中的值"""
if isinstance(d, dict):
for k, v in d.items():
# 检查键名
for pattern in key_patterns:
if re.search(pattern, k, re.IGNORECASE):
return v
# 递归搜索值
if isinstance(v, (dict, list)):
found = search_dict(v, key_patterns)
if found:
return found
elif isinstance(d, list):
for item in d:
found = search_dict(item, key_patterns)
if found:
return found
return None
# 搜索运单号
waybill = search_dict(json_data, [r'waybill', r'tracking.*number', r'运单号', r'waybillNo'])
if waybill:
result['waybill_no'] = str(waybill)
# 搜索承运人
carrier = search_dict(json_data, [r'carrier', r'承运人', r'carrierName'])
if carrier:
result['carrier'] = str(carrier)
# 搜索承运人电话
phone = search_dict(json_data, [r'carrier.*phone', r'承运人电话', r'carrierPhone', r'phone'])
if phone:
result['carrier_phone'] = str(phone)
# 搜索物流跟踪信息
tracking = search_dict(json_data, [r'track', r'logistics', r'物流', r'轨迹', r'history'])
if tracking:
if isinstance(tracking, list):
result['tracking_info'] = tracking
elif isinstance(tracking, dict):
result['tracking_info'] = [tracking]
return result if result else None
def print_result(result):
"""打印提取结果"""
if not result:
print("未能提取到物流信息")
return
print("\n" + "="*50)
print("物流信息提取结果:")
print("="*50)
print(f"运单号: {result.get('waybill_no', '未找到')}")
print(f"国内承运人: {result.get('carrier', '未找到')}")
print(f"国内承运人电话: {result.get('carrier_phone', '未找到')}")
if result.get('tracking_info'):
print(f"\n物流跟踪信息 (共 {len(result['tracking_info'])} 条):")
for idx, info in enumerate(result['tracking_info'], 1):
if isinstance(info, dict):
text = info.get('text', str(info))
time_str = info.get('time', '')
print(f" {idx}. {text}")
if time_str:
print(f" 时间: {time_str}")
else:
print(f" {idx}. {info}")
else:
print("\n物流跟踪信息: 未找到")
print("="*50)
# 主程序
if __name__ == '__main__':
# 测试 URL
tracking_url = "https://3.cn/2t-Iibig"
print("="*60)
print("京东物流信息提取工具 (Ubuntu 版本)")
print("="*60)
print(f"目标 URL: {tracking_url}")
print(f"无头模式: {'' if USE_HEADLESS else ''}")
print("开始提取物流信息...\n")
try:
result = extract_logistics_info(tracking_url)
except Exception as e:
print(f"\n❌ 执行过程中出错: {e}")
import traceback
traceback.print_exc()
result = None
if result:
print_result(result)
# 保存结果到文件
output_file = "logistics_result.json"
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(result, f, ensure_ascii=False, indent=2)
print(f"\n结果已保存到: {output_file}")
else:
print("提取失败")
print("\n脚本执行完成")

View File

@@ -0,0 +1,457 @@
import time
import json
import re
import os
import platform
import threading
from flask import Flask, request, jsonify
from DrissionPage import ChromiumPage, ChromiumOptions
# Ubuntu 上常见的 Chrome/Chromium 路径
UBUNTU_CHROME_PATHS = [
'/usr/bin/google-chrome',
'/usr/bin/google-chrome-stable',
'/usr/bin/chromium-browser',
'/usr/bin/chromium',
'/snap/bin/chromium',
'/opt/google/chrome/chrome',
]
# 是否使用无头模式headless
# True: 无界面模式,适合服务器环境
# False: 有界面模式,需要 X11 或 Wayland
USE_HEADLESS = True # 可以根据需要修改
# 监听端口:内网多实例时每台设不同端口,例如 LOGISTICS_PORT=5002
LISTEN_PORT = int(os.environ.get('LOGISTICS_PORT', os.environ.get('PORT', '5001')))
# 全局浏览器实例
global_page = None
def find_chrome_path():
"""自动查找 Ubuntu 系统中的 Chrome/Chromium 路径"""
print("正在查找 Chrome/Chromium 浏览器...")
# 首先尝试常见的路径
for path in UBUNTU_CHROME_PATHS:
if os.path.exists(path):
print(f"✅ 找到浏览器: {path}")
return path
# 尝试使用 which 命令查找
import subprocess
try:
result = subprocess.run(['which', 'google-chrome'],
capture_output=True, text=True, timeout=5)
if result.returncode == 0 and os.path.exists(result.stdout.strip()):
path = result.stdout.strip()
print(f"✅ 通过 which 找到浏览器: {path}")
return path
except:
pass
try:
result = subprocess.run(['which', 'chromium-browser'],
capture_output=True, text=True, timeout=5)
if result.returncode == 0 and os.path.exists(result.stdout.strip()):
path = result.stdout.strip()
print(f"✅ 通过 which 找到浏览器: {path}")
return path
except:
pass
# 如果都找不到,返回最常见的路径
default_path = '/usr/bin/google-chrome'
print(f"⚠️ 未找到浏览器,将使用默认路径: {default_path}")
print("请确保已安装 Google Chrome 或 Chromium:")
print(" sudo apt update")
print(" sudo apt install -y google-chrome-stable")
print(" 或者")
print(" sudo apt install -y chromium-browser")
return default_path
def get_global_browser():
"""获取全局浏览器实例Ubuntu 版本)"""
global global_page
if global_page is None:
print("="*60)
print("Ubuntu 浏览器初始化")
print("="*60)
# 检查操作系统
if platform.system() != 'Linux':
print(f"⚠️ 警告: 当前系统是 {platform.system()},此脚本专为 Ubuntu 设计")
# 查找 Chrome 路径
chrome_path = find_chrome_path()
options = ChromiumOptions()
options.set_browser_path(chrome_path)
# Ubuntu 服务器环境通常使用无头模式
if USE_HEADLESS:
print("配置为无头模式headless...")
try:
options.headless(True)
except:
# 如果 headless 方法不存在,使用参数
try:
options.set_argument('--headless=new')
options.set_argument('--no-sandbox')
options.set_argument('--disable-dev-shm-usage')
except:
pass
else:
print("配置为有界面模式...")
# 检查是否有显示环境
display = os.environ.get('DISPLAY')
if not display:
print("⚠️ 警告: 未检测到 DISPLAY 环境变量")
print("如果无法显示浏览器,请:")
print(" 1. 设置 USE_HEADLESS = True")
print(" 2. 或者设置 DISPLAY 环境变量(如 DISPLAY=:0")
print(" 3. 或者使用 Xvfb虚拟显示")
# Linux 特定参数
try:
options.set_argument('--no-sandbox') # 在某些环境下需要
options.set_argument('--disable-dev-shm-usage') # 避免 /dev/shm 空间不足
options.set_argument('--disable-gpu') # 禁用 GPU可选在 headless 模式下有用)
except:
pass
print(f"正在启动浏览器...")
print(f"浏览器路径: {chrome_path}")
if USE_HEADLESS:
print("模式: 无头模式(后台运行)")
else:
print("模式: 有界面模式")
try:
global_page = ChromiumPage(options)
print("✅ 浏览器已成功启动!")
time.sleep(2) # 等待浏览器完全启动
except Exception as e:
print(f"❌ 浏览器启动失败: {e}")
print("\n可能的解决方案:")
print("1. 确保已安装 Chrome/Chromium:")
print(" sudo apt update")
print(" sudo apt install -y google-chrome-stable")
print("2. 如果使用无头模式失败,尝试设置 USE_HEADLESS = False")
print("3. 确保有足够的权限")
print("4. 检查是否缺少依赖:")
print(" sudo apt install -y libnss3 libatk-bridge2.0-0 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libasound2")
import traceback
traceback.print_exc()
raise
else:
print("使用已存在的浏览器实例")
return global_page
def extract_logistics_info(tracking_url):
"""
从京东物流追踪页面提取运单号、承运人等信息Ubuntu 版本)
Args:
tracking_url: 物流追踪页面 URL例如 https://3.cn/2t-Iibig
Returns:
dict: 包含运单号、承运人、承运人电话、物流跟踪信息等的字典
"""
page = get_global_browser()
try:
print(f"\n正在打开物流追踪页面: {tracking_url}")
page.get(tracking_url)
print("页面加载中,请稍候...")
time.sleep(5) # 等待页面加载
# 检查页面是否成功加载
current_url = page.url
print(f"当前页面 URL: {current_url}")
# 检查页面标题
try:
title = page.title
print(f"页面标题: {title}")
except:
print("无法获取页面标题")
# 检查页面是否有内容
try:
html_length = len(page.html)
print(f"页面 HTML 长度: {html_length} 字符")
if html_length < 100:
print("⚠️ 警告: 页面内容可能未完全加载")
except Exception as e:
print(f"⚠️ 无法获取页面 HTML: {e}")
result = {
"waybill_no": None, # 运单号
"carrier": None, # 国内承运人
"carrier_phone": None, # 国内承运人电话
"tracking_info": [], # 物流跟踪信息列表
}
# 从 DOM 元素中提取数据
print("\n从 DOM 元素提取数据...")
# 尝试查找运单号元素
waybill_elements = page.eles('xpath=//*[contains(text(), "运单号") or contains(text(), "运单")]')
for elem in waybill_elements:
text = elem.text
parent_text = elem.parent().text if elem.parent() else ""
full_text = text + " " + parent_text
# 从文本中提取数字作为运单号
numbers = re.findall(r'\d{8,}', full_text)
if numbers and not result['waybill_no']:
result['waybill_no'] = numbers[0]
print(f"✅ 找到运单号: {result['waybill_no']}")
# 提取承运人
if '承运人' in text and not result['carrier']:
carrier_match = re.search(r'承运人[:\s]*([^\s<,]+)', full_text)
if carrier_match:
result['carrier'] = carrier_match.group(1).strip()
print(f"✅ 找到承运人: {result['carrier']}")
# 提取电话
if '电话' in text and not result['carrier_phone']:
phone_match = re.search(r'电话[:\s]*(\d+)', full_text)
if phone_match:
result['carrier_phone'] = phone_match.group(1)
print(f"✅ 找到承运人电话: {result['carrier_phone']}")
# 提取物流跟踪信息(时间线)
print("\n提取物流跟踪信息...")
tracking_elements = page.eles('xpath=//*[contains(@class, "track") or contains(@class, "logistics") or contains(@class, "timeline")]')
if not tracking_elements:
# 尝试查找包含时间戳的元素
tracking_elements = page.eles('xpath=//*[contains(text(), "2025") or contains(text(), "货物") or contains(text(), "到达")]')
tracking_info = []
for elem in tracking_elements[:20]: # 限制数量
text = elem.text
if text and len(text) > 5:
# 尝试提取时间戳
time_match = re.search(r'(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})', text)
if time_match or any(keyword in text for keyword in ['货物', '到达', '揽收', '运输', '配送', '签收']):
tracking_info.append({
'text': text.strip(),
'time': time_match.group(1) if time_match else None
})
result['tracking_info'] = tracking_info[:10] # 最多保存10条
if result['tracking_info']:
print(f"✅ 找到 {len(result['tracking_info'])} 条物流跟踪信息")
return result
except Exception as e:
print(f"提取物流信息时出错: {e}")
import traceback
traceback.print_exc()
return None
def print_result(result):
"""打印提取结果"""
if not result:
print("未能提取到物流信息")
return
print("\n" + "="*50)
print("物流信息提取结果:")
print("="*50)
print(f"运单号: {result.get('waybill_no', '未找到')}")
print(f"国内承运人: {result.get('carrier', '未找到')}")
print(f"国内承运人电话: {result.get('carrier_phone', '未找到')}")
if result.get('tracking_info'):
print(f"\n物流跟踪信息 (共 {len(result['tracking_info'])} 条):")
for idx, info in enumerate(result['tracking_info'], 1):
if isinstance(info, dict):
text = info.get('text', str(info))
time_str = info.get('time', '')
print(f" {idx}. {text}")
if time_str:
print(f" 时间: {time_str}")
else:
print(f" {idx}. {info}")
else:
print("\n物流跟踪信息: 未找到")
print("="*50)
# =================== Flask API 接口 ===================
# 初始化 Flask 应用
app = Flask(__name__)
# 初始化锁,防止并发访问
fetch_lock = threading.Lock()
@app.route('/fetch_logistics', methods=['GET', 'POST'])
def fetch_logistics():
"""
查询物流信息接口
参数:
tracking_url: 物流追踪页面 URLGET 或 POST
例如: https://3.cn/2t-Iibig
返回:
JSON 格式的物流信息,包含:
- waybill_no: 运单号
- carrier: 国内承运人
- carrier_phone: 国内承运人电话
- tracking_info: 物流跟踪信息列表
- success: 是否成功
- message: 消息提示
"""
# 获取参数(支持 GET 和 POST
if request.method == 'POST':
if request.is_json:
data = request.get_json()
tracking_url = data.get('tracking_url') or data.get('url')
else:
tracking_url = request.form.get('tracking_url') or request.form.get('url') or request.args.get('tracking_url') or request.args.get('url')
else:
tracking_url = request.args.get('tracking_url') or request.args.get('url')
if not tracking_url:
return jsonify({
"success": False,
"error": "缺少参数 tracking_url 或 url",
"message": "请提供物流追踪页面 URL"
}), 400
# 验证 URL 格式
if not (tracking_url.startswith('http://') or tracking_url.startswith('https://')):
return jsonify({
"success": False,
"error": "URL 格式错误",
"message": "URL 必须以 http:// 或 https:// 开头"
}), 400
try:
with fetch_lock: # 加锁,防止并发调用
print(f"\n收到物流查询请求: {tracking_url}")
result = extract_logistics_info(tracking_url)
if result:
# 构建返回数据
response_data = {
"success": True,
"message": "查询成功",
"data": {
"waybill_no": result.get('waybill_no'),
"carrier": result.get('carrier'),
"carrier_phone": result.get('carrier_phone'),
"tracking_info": result.get('tracking_info', []),
"tracking_count": len(result.get('tracking_info', []))
},
"url": tracking_url
}
# 如果有些信息未找到,添加提示
missing_fields = []
if not result.get('waybill_no'):
missing_fields.append('waybill_no')
if not result.get('carrier'):
missing_fields.append('carrier')
if missing_fields:
response_data["warning"] = f"以下字段未找到: {', '.join(missing_fields)}"
return jsonify(response_data), 200
else:
return jsonify({
"success": False,
"error": "提取失败",
"message": "未能从页面中提取到物流信息",
"url": tracking_url
}), 500
except Exception as e:
print(f"查询物流信息时出错: {e}")
import traceback
traceback.print_exc()
return jsonify({
"success": False,
"error": str(e),
"message": "服务器内部错误",
"url": tracking_url
}), 500
@app.route('/health', methods=['GET'])
def health():
"""健康检查接口"""
return jsonify({
"status": "ok",
"service": "京东物流信息查询服务",
"version": "1.0.0"
}), 200
@app.route('/', methods=['GET'])
def index():
"""首页,返回 API 使用说明"""
return jsonify({
"service": "京东物流信息查询 API",
"version": "1.0.0",
"endpoints": {
"/fetch_logistics": {
"method": ["GET", "POST"],
"description": "查询物流信息",
"parameters": {
"tracking_url": "物流追踪页面 URL必需",
"url": "tracking_url 的别名(可选)"
},
"example_get": "/fetch_logistics?tracking_url=https://3.cn/2t-Iibig",
"example_post": "POST /fetch_logistics\n{\"tracking_url\": \"https://3.cn/2t-Iibig\"}"
},
"/health": {
"method": ["GET"],
"description": "健康检查"
}
}
}), 200
# =================== 启动服务 ===================
if __name__ == '__main__':
# API 服务模式(默认)
print("="*60)
print("京东物流信息查询 API 服务 (Ubuntu 版本)")
print("="*60)
print(f"无头模式: {'' if USE_HEADLESS else ''}")
print("\n服务接口:")
print(" GET/POST /fetch_logistics?tracking_url=<URL> - 查询物流信息")
print(" GET /health - 健康检查")
print(" GET / - API 说明")
print("\n启动服务...")
print(f"服务地址: http://0.0.0.0:{LISTEN_PORT} (环境变量 LOGISTICS_PORT / PORT 可覆盖)")
print("按 Ctrl+C 停止服务\n")
try:
app.run(host='0.0.0.0', port=LISTEN_PORT, debug=False, threaded=True)
except KeyboardInterrupt:
print("\n\n服务已停止")
finally:
if 'global_page' in globals() and global_page:
try:
global_page.quit()
print("浏览器已关闭")
except:
pass

68
logistics.log Normal file
View File

@@ -0,0 +1,68 @@
[2025-12-29 21:46:15] [INFO] 开始执行物流提取脚本
[2025-12-29 21:46:15] [INFO] 激活虚拟环境: /home/van/project/jd_python/venv
[2025-12-29 21:46:15] [INFO] Python版本: Python 3.12.3
[2025-12-29 21:46:15] [INFO] 执行脚本: /home/van/project/jd_python/jd/fetch_logistics_ubuntu.py
[2026-01-13 21:17:03] [INFO] 开始执行物流提取脚本
[2026-01-13 21:17:03] [INFO] 激活虚拟环境: /home/van/project/jd_python/venv
[2026-01-13 21:17:03] [INFO] Python版本: Python 3.12.3
[2026-01-13 21:17:03] [INFO] 执行脚本: /home/van/project/jd_python/jd/fetch_logistics_ubuntu.py
[2026-01-17 17:39:45] [INFO] 开始执行物流提取脚本
[2026-01-17 17:39:45] [INFO] 激活虚拟环境: /home/van/project/jd_python/venv
[2026-01-17 17:39:45] [INFO] Python版本: Python 3.12.3
[2026-01-17 17:39:45] [INFO] 执行脚本: /home/van/project/jd_python/jd/fetch_logistics_ubuntu.py
[2026-01-19 15:06:09] [INFO] 开始执行物流提取脚本
[2026-01-19 15:06:09] [INFO] 激活虚拟环境: /home/van/project/jd_python/venv
[2026-01-19 15:06:09] [INFO] Python版本: Python 3.12.3
[2026-01-19 15:06:09] [INFO] 执行脚本: /home/van/project/jd_python/jd/fetch_logistics_ubuntu.py
[2026-01-19 15:25:02] [INFO] 开始执行物流提取脚本
[2026-01-19 15:25:02] [INFO] 激活虚拟环境: /home/van/project/jd_python/venv
[2026-01-19 15:25:02] [INFO] Python版本: Python 3.12.3
[2026-01-19 15:25:02] [INFO] 执行脚本: /home/van/project/jd_python/jd/fetch_logistics_ubuntu.py
[2026-01-19 18:59:37] [INFO] 开始执行物流提取脚本
[2026-01-19 18:59:37] [INFO] 激活虚拟环境: /home/van/project/jd_python/venv
[2026-01-19 18:59:37] [INFO] Python版本: Python 3.12.3
[2026-01-19 18:59:37] [INFO] 执行脚本: /home/van/project/jd_python/jd/fetch_logistics_ubuntu.py
[2026-02-09 18:06:44] [INFO] 开始执行物流提取脚本
[2026-02-09 18:06:44] [INFO] 激活虚拟环境: /home/van/project/jd_python/venv
[2026-02-09 18:06:44] [INFO] Python版本: Python 3.12.3
[2026-02-09 18:06:44] [INFO] 执行脚本: /home/van/project/jd_python/jd/fetch_logistics_ubuntu.py
[2026-02-11 14:32:19] [INFO] 开始执行物流提取脚本
[2026-02-11 14:32:19] [INFO] 激活虚拟环境: /home/van/project/jd_python/venv
[2026-02-11 14:32:19] [INFO] Python版本: Python 3.12.3
[2026-02-11 14:32:19] [INFO] 执行脚本: /home/van/project/jd_python/jd/fetch_logistics_ubuntu.py
[2026-02-11 16:45:00] [INFO] 开始执行物流提取脚本
[2026-02-11 16:45:00] [INFO] 激活虚拟环境: /home/van/project/jd_python/venv
[2026-02-11 16:45:00] [INFO] Python版本: Python 3.12.3
[2026-02-11 16:45:00] [INFO] 执行脚本: /home/van/project/jd_python/jd/fetch_logistics_ubuntu.py
[2026-02-12 01:35:07] [INFO] 开始执行物流提取脚本
[2026-02-12 01:35:07] [INFO] 激活虚拟环境: /home/van/project/jd_python/venv
[2026-02-12 01:35:07] [INFO] Python版本: Python 3.12.3
[2026-02-12 01:35:07] [INFO] 执行脚本: /home/van/project/jd_python/jd/fetch_logistics_ubuntu.py
[2026-02-15 10:27:16] [INFO] 开始执行物流提取脚本
[2026-02-15 10:27:16] [INFO] 激活虚拟环境: /home/van/project/jd_python/venv
[2026-02-15 10:27:16] [INFO] Python版本: Python 3.12.3
[2026-02-15 10:27:16] [INFO] 执行脚本: /home/van/project/jd_python/jd/fetch_logistics_ubuntu.py
[2026-02-24 19:34:46] [INFO] 开始执行物流提取脚本
[2026-02-24 19:34:46] [INFO] 激活虚拟环境: /home/van/project/jd_python/venv
[2026-02-24 19:34:46] [INFO] Python版本: Python 3.12.3
[2026-02-24 19:34:46] [INFO] 执行脚本: /home/van/project/jd_python/jd/fetch_logistics_ubuntu.py
[2026-02-27 17:42:56] [INFO] 开始执行物流提取脚本
[2026-02-27 17:42:56] [INFO] 激活虚拟环境: /home/van/project/jd_python/venv
[2026-02-27 17:42:56] [INFO] Python版本: Python 3.12.3
[2026-02-27 17:42:56] [INFO] 执行脚本: /home/van/project/jd_python/jd/fetch_logistics_ubuntu.py
[2026-02-28 16:39:40] [INFO] 开始执行物流提取脚本
[2026-02-28 16:39:40] [INFO] 激活虚拟环境: /home/van/project/jd_python/venv
[2026-02-28 16:39:40] [INFO] Python版本: Python 3.12.3
[2026-02-28 16:39:40] [INFO] 执行脚本: /home/van/project/jd_python/jd/fetch_logistics_ubuntu.py
[2026-03-30 14:57:26] [INFO] 开始执行物流提取脚本
[2026-03-30 14:57:26] [INFO] 激活虚拟环境: /home/van/project/jd_python/venv
[2026-03-30 14:57:26] [INFO] Python版本: Python 3.12.3
[2026-03-30 14:57:26] [INFO] 执行脚本: /home/van/project/jd_python/jd/fetch_logistics_ubuntu.py
[2026-04-07 08:00:51] [INFO] 开始执行物流提取脚本
[2026-04-07 08:00:51] [INFO] 激活虚拟环境: /home/van/project/jd_python/venv
[2026-04-07 08:00:51] [INFO] Python版本: Python 3.12.3
[2026-04-07 08:00:51] [INFO] 执行脚本: /home/van/project/jd_python/jd/fetch_logistics_ubuntu.py
[2026-04-07 12:11:33] [INFO] 开始执行物流提取脚本
[2026-04-07 12:11:33] [INFO] 激活虚拟环境: /home/van/project/jd_python/venv
[2026-04-07 12:11:33] [INFO] Python版本: Python 3.12.3
[2026-04-07 12:11:33] [INFO] 执行脚本: /home/van/project/jd_python/jd/fetch_logistics_ubuntu.py

48
logistics_result.json Normal file

File diff suppressed because one or more lines are too long

105
run_logistics.sh Normal file
View File

@@ -0,0 +1,105 @@
#!/usr/bin/env bash
# =============================================================================
# 物流提取脚本 - systemd 优化版
# =============================================================================
#
# 同机多实例(并行查询):每个进程独立 Chrome进程内仍是串行抓取不是「单进程多线程并行」。
# Java 侧配置逗号分隔多个地址,例如:
# jarvis.server.logistics.base-urls=http://127.0.0.1:5001,http://127.0.0.1:5002,http://127.0.0.1:5003
#
# 三端口示例(建议每实例单独 LOG_FILE避免日志交错
# LOGISTICS_PORT=5001 LOG_FILE="${PWD}/logistics-5001.log" ./run_logistics.sh
# LOGISTICS_PORT=5002 LOG_FILE="${PWD}/logistics-5002.log" ./run_logistics.sh
# LOGISTICS_PORT=5003 LOG_FILE="${PWD}/logistics-5003.log" ./run_logistics.sh
#
# systemd复制三份 service分别设置 Environment=LOGISTICS_PORT=500x 与不同的日志路径。
# =============================================================================
set -euo pipefail
# 配置部分
readonly SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
readonly VENV_DIR="${VENV_DIR:-${SCRIPT_DIR}/venv}"
readonly PYTHON_SCRIPT="${PYTHON_SCRIPT:-${SCRIPT_DIR}/jd/fetch_logistics_ubuntu.py}"
readonly LOG_FILE="${LOG_FILE:-${SCRIPT_DIR}/logistics.log}"
# 供 jd/fetch_logistics_ubuntu.py 读取(默认 5001
export LOGISTICS_PORT="${LOGISTICS_PORT:-5001}"
# 日志函数
log_info() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] $*" | tee -a "${LOG_FILE}"
}
log_error() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $*" | tee -a "${LOG_FILE}" >&2
}
# 检查文件是否存在
check_file() {
if [[ ! -f "$1" ]]; then
log_error "文件不存在: $1"
return 1
fi
return 0
}
# 检查目录是否存在
check_dir() {
if [[ ! -d "$1" ]]; then
log_error "目录不存在: $1"
return 1
fi
return 0
}
# 主函数
main() {
log_info "开始执行物流提取脚本 (LOGISTICS_PORT=${LOGISTICS_PORT})"
# 切换到脚本目录
cd "${SCRIPT_DIR}" || {
log_error "无法切换到目录: ${SCRIPT_DIR}"
return 1
}
# 检查虚拟环境
if ! check_dir "${VENV_DIR}"; then
return 1
fi
# 检查Python脚本
if ! check_file "${PYTHON_SCRIPT}"; then
return 1
fi
# 激活虚拟环境
log_info "激活虚拟环境: ${VENV_DIR}"
source "${VENV_DIR}/bin/activate" || {
log_error "无法激活虚拟环境"
return 1
}
# 检查Python版本
python_version=$(python --version 2>&1)
log_info "Python版本: ${python_version}"
# 运行Python脚本
log_info "执行脚本: ${PYTHON_SCRIPT}"
# 传递所有参数给Python脚本
if python "${PYTHON_SCRIPT}" "$@"; then
log_info "脚本执行成功"
return 0
else
log_error "脚本执行失败"
return 1
fi
}
# 执行
if main "$@"; then
exit 0
else
exit 1
fi

153
setup_ubuntu.sh Normal file
View File

@@ -0,0 +1,153 @@
#!/bin/bash
# Ubuntu 环境快速设置脚本
set -e # 遇到错误立即退出
# 确保使用 bash 运行(兼容性问题处理)
if [ -z "$BASH_VERSION" ]; then
echo "警告: 此脚本需要使用 bash 运行"
echo "请使用: bash $0"
exit 1
fi
echo "=========================================="
echo "京东物流提取工具 - Ubuntu 环境设置"
echo "=========================================="
echo ""
# 1. 检查并安装系统依赖
echo "步骤 1: 检查系统依赖..."
if ! command -v python3 >/dev/null 2>&1; then
echo "安装 Python3..."
sudo apt update
sudo apt install -y python3 python3-pip python3-venv
else
echo "✅ Python3 已安装"
fi
# 检查 Chrome/Chromium
CHROME_PATH=""
if command -v google-chrome >/dev/null 2>&1; then
CHROME_PATH=$(which google-chrome)
echo "✅ 找到 Google Chrome: $CHROME_PATH"
elif [ -f "/usr/bin/google-chrome" ]; then
CHROME_PATH="/usr/bin/google-chrome"
echo "✅ 找到 Google Chrome: $CHROME_PATH"
elif command -v chromium-browser >/dev/null 2>&1; then
CHROME_PATH=$(which chromium-browser)
echo "✅ 找到 Chromium: $CHROME_PATH"
elif [ -f "/usr/bin/chromium-browser" ]; then
CHROME_PATH="/usr/bin/chromium-browser"
echo "✅ 找到 Chromium: $CHROME_PATH"
else
echo "⚠️ 未找到 Chrome/Chromium将尝试安装..."
echo "选择要安装的浏览器:"
echo "1) Google Chrome (推荐)"
echo "2) Chromium (开源版本)"
read -p "请选择 [1-2]: " choice
if [ "$choice" = "1" ]; then
echo "正在安装 Google Chrome..."
wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo apt install -y ./google-chrome-stable_current_amd64.deb
rm -f google-chrome-stable_current_amd64.deb
CHROME_PATH="/usr/bin/google-chrome"
elif [ "$choice" = "2" ]; then
echo "正在安装 Chromium..."
sudo apt update
sudo apt install -y chromium-browser
CHROME_PATH="/usr/bin/chromium-browser"
fi
fi
# 2. 安装 Chrome 运行时依赖
echo ""
echo "步骤 2: 检查 Chrome 运行时依赖..."
DEPS="libnss3 libatk-bridge2.0-0 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libasound2"
MISSING_DEPS=""
for dep in $DEPS; do
if ! dpkg -l 2>/dev/null | grep -q "^ii.*$dep"; then
if [ -z "$MISSING_DEPS" ]; then
MISSING_DEPS="$dep"
else
MISSING_DEPS="$MISSING_DEPS $dep"
fi
fi
done
if [ -n "$MISSING_DEPS" ]; then
echo "安装缺失的依赖: $MISSING_DEPS"
sudo apt install -y $MISSING_DEPS
else
echo "✅ 所有依赖已安装"
fi
# 3. 创建虚拟环境
echo ""
echo "步骤 3: 设置 Python 虚拟环境..."
if [ ! -d "venv" ]; then
echo "创建虚拟环境..."
python3 -m venv venv
echo "✅ 虚拟环境创建成功"
else
echo "✅ 虚拟环境已存在"
fi
# 4. 激活虚拟环境并安装 Python 包
echo ""
echo "步骤 4: 安装 Python 依赖包..."
source venv/bin/activate
# 升级 pip
pip install --upgrade pip
# 安装依赖
pip install DrissionPage
# 可选:如果需要数据库功能
read -p "是否需要数据库功能?(sqlalchemy, pymysql) [y/N]: " need_db
if [ "$need_db" = "y" ] || [ "$need_db" = "Y" ]; then
pip install sqlalchemy pymysql
fi
deactivate
# 5. 创建运行脚本
echo ""
echo "步骤 5: 创建便捷运行脚本..."
cat > run_logistics.sh << 'EOF'
#!/bin/bash
# 激活虚拟环境并运行物流提取脚本
cd "$(dirname "$0")"
source venv/bin/activate
# 运行脚本
python jd/fetch_logistics_ubuntu.py "$@"
deactivate
EOF
chmod +x run_logistics.sh
# 6. 完成
echo ""
echo "=========================================="
echo "✅ 环境设置完成!"
echo "=========================================="
echo ""
echo "快速开始:"
echo " 方式1: 使用便捷脚本"
echo " ./run_logistics.sh"
echo ""
echo " 方式2: 手动运行"
echo " source venv/bin/activate"
echo " python jd/fetch_logistics_ubuntu.py"
echo " deactivate"
echo ""
echo "浏览器路径: $CHROME_PATH"
echo "虚拟环境: $(pwd)/venv"
echo ""

247
venv/bin/Activate.ps1 Normal file
View File

@@ -0,0 +1,247 @@
<#
.Synopsis
Activate a Python virtual environment for the current PowerShell session.
.Description
Pushes the python executable for a virtual environment to the front of the
$Env:PATH environment variable and sets the prompt to signify that you are
in a Python virtual environment. Makes use of the command line switches as
well as the `pyvenv.cfg` file values present in the virtual environment.
.Parameter VenvDir
Path to the directory that contains the virtual environment to activate. The
default value for this is the parent of the directory that the Activate.ps1
script is located within.
.Parameter Prompt
The prompt prefix to display when this virtual environment is activated. By
default, this prompt is the name of the virtual environment folder (VenvDir)
surrounded by parentheses and followed by a single space (ie. '(.venv) ').
.Example
Activate.ps1
Activates the Python virtual environment that contains the Activate.ps1 script.
.Example
Activate.ps1 -Verbose
Activates the Python virtual environment that contains the Activate.ps1 script,
and shows extra information about the activation as it executes.
.Example
Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
Activates the Python virtual environment located in the specified location.
.Example
Activate.ps1 -Prompt "MyPython"
Activates the Python virtual environment that contains the Activate.ps1 script,
and prefixes the current prompt with the specified string (surrounded in
parentheses) while the virtual environment is active.
.Notes
On Windows, it may be required to enable this Activate.ps1 script by setting the
execution policy for the user. You can do this by issuing the following PowerShell
command:
PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
For more information on Execution Policies:
https://go.microsoft.com/fwlink/?LinkID=135170
#>
Param(
[Parameter(Mandatory = $false)]
[String]
$VenvDir,
[Parameter(Mandatory = $false)]
[String]
$Prompt
)
<# Function declarations --------------------------------------------------- #>
<#
.Synopsis
Remove all shell session elements added by the Activate script, including the
addition of the virtual environment's Python executable from the beginning of
the PATH variable.
.Parameter NonDestructive
If present, do not remove this function from the global namespace for the
session.
#>
function global:deactivate ([switch]$NonDestructive) {
# Revert to original values
# The prior prompt:
if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
}
# The prior PYTHONHOME:
if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
}
# The prior PATH:
if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
Remove-Item -Path Env:_OLD_VIRTUAL_PATH
}
# Just remove the VIRTUAL_ENV altogether:
if (Test-Path -Path Env:VIRTUAL_ENV) {
Remove-Item -Path env:VIRTUAL_ENV
}
# Just remove VIRTUAL_ENV_PROMPT altogether.
if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {
Remove-Item -Path env:VIRTUAL_ENV_PROMPT
}
# Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
}
# Leave deactivate function in the global namespace if requested:
if (-not $NonDestructive) {
Remove-Item -Path function:deactivate
}
}
<#
.Description
Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
given folder, and returns them in a map.
For each line in the pyvenv.cfg file, if that line can be parsed into exactly
two strings separated by `=` (with any amount of whitespace surrounding the =)
then it is considered a `key = value` line. The left hand string is the key,
the right hand is the value.
If the value starts with a `'` or a `"` then the first and last character is
stripped from the value before being captured.
.Parameter ConfigDir
Path to the directory that contains the `pyvenv.cfg` file.
#>
function Get-PyVenvConfig(
[String]
$ConfigDir
) {
Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
# Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
$pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
# An empty map will be returned if no config file is found.
$pyvenvConfig = @{ }
if ($pyvenvConfigPath) {
Write-Verbose "File exists, parse `key = value` lines"
$pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
$pyvenvConfigContent | ForEach-Object {
$keyval = $PSItem -split "\s*=\s*", 2
if ($keyval[0] -and $keyval[1]) {
$val = $keyval[1]
# Remove extraneous quotations around a string value.
if ("'""".Contains($val.Substring(0, 1))) {
$val = $val.Substring(1, $val.Length - 2)
}
$pyvenvConfig[$keyval[0]] = $val
Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
}
}
}
return $pyvenvConfig
}
<# Begin Activate script --------------------------------------------------- #>
# Determine the containing directory of this script
$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
$VenvExecDir = Get-Item -Path $VenvExecPath
Write-Verbose "Activation script is located in path: '$VenvExecPath'"
Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
# Set values required in priority: CmdLine, ConfigFile, Default
# First, get the location of the virtual environment, it might not be
# VenvExecDir if specified on the command line.
if ($VenvDir) {
Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
}
else {
Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
Write-Verbose "VenvDir=$VenvDir"
}
# Next, read the `pyvenv.cfg` file to determine any required value such
# as `prompt`.
$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
# Next, set the prompt from the command line, or the config file, or
# just use the name of the virtual environment folder.
if ($Prompt) {
Write-Verbose "Prompt specified as argument, using '$Prompt'"
}
else {
Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
$Prompt = $pyvenvCfg['prompt'];
}
else {
Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)"
Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
$Prompt = Split-Path -Path $venvDir -Leaf
}
}
Write-Verbose "Prompt = '$Prompt'"
Write-Verbose "VenvDir='$VenvDir'"
# Deactivate any currently active virtual environment, but leave the
# deactivate function in place.
deactivate -nondestructive
# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
# that there is an activated venv.
$env:VIRTUAL_ENV = $VenvDir
if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
Write-Verbose "Setting prompt to '$Prompt'"
# Set the prompt to include the env name
# Make sure _OLD_VIRTUAL_PROMPT is global
function global:_OLD_VIRTUAL_PROMPT { "" }
Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
function global:prompt {
Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
_OLD_VIRTUAL_PROMPT
}
$env:VIRTUAL_ENV_PROMPT = $Prompt
}
# Clear PYTHONHOME
if (Test-Path -Path Env:PYTHONHOME) {
Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
Remove-Item -Path Env:PYTHONHOME
}
# Add the venv to the PATH
Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"

70
venv/bin/activate Normal file
View File

@@ -0,0 +1,70 @@
# This file must be used with "source bin/activate" *from bash*
# You cannot run it directly
deactivate () {
# reset old environment variables
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
PATH="${_OLD_VIRTUAL_PATH:-}"
export PATH
unset _OLD_VIRTUAL_PATH
fi
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
export PYTHONHOME
unset _OLD_VIRTUAL_PYTHONHOME
fi
# Call hash to forget past commands. Without forgetting
# past commands the $PATH changes we made may not be respected
hash -r 2> /dev/null
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
PS1="${_OLD_VIRTUAL_PS1:-}"
export PS1
unset _OLD_VIRTUAL_PS1
fi
unset VIRTUAL_ENV
unset VIRTUAL_ENV_PROMPT
if [ ! "${1:-}" = "nondestructive" ] ; then
# Self destruct!
unset -f deactivate
fi
}
# unset irrelevant variables
deactivate nondestructive
# on Windows, a path can contain colons and backslashes and has to be converted:
if [ "${OSTYPE:-}" = "cygwin" ] || [ "${OSTYPE:-}" = "msys" ] ; then
# transform D:\path\to\venv to /d/path/to/venv on MSYS
# and to /cygdrive/d/path/to/venv on Cygwin
export VIRTUAL_ENV=$(cygpath /home/van/project/jd_python/venv)
else
# use the path as-is
export VIRTUAL_ENV=/home/van/project/jd_python/venv
fi
_OLD_VIRTUAL_PATH="$PATH"
PATH="$VIRTUAL_ENV/"bin":$PATH"
export PATH
# unset PYTHONHOME if set
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
# could use `if (set -u; : $PYTHONHOME) ;` in bash
if [ -n "${PYTHONHOME:-}" ] ; then
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
unset PYTHONHOME
fi
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
_OLD_VIRTUAL_PS1="${PS1:-}"
PS1='(venv) '"${PS1:-}"
export PS1
VIRTUAL_ENV_PROMPT='(venv) '
export VIRTUAL_ENV_PROMPT
fi
# Call hash to forget past commands. Without forgetting
# past commands the $PATH changes we made may not be respected
hash -r 2> /dev/null

27
venv/bin/activate.csh Normal file
View File

@@ -0,0 +1,27 @@
# This file must be used with "source bin/activate.csh" *from csh*.
# You cannot run it directly.
# Created by Davide Di Blasi <davidedb@gmail.com>.
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate'
# Unset irrelevant variables.
deactivate nondestructive
setenv VIRTUAL_ENV /home/van/project/jd_python/venv
set _OLD_VIRTUAL_PATH="$PATH"
setenv PATH "$VIRTUAL_ENV/"bin":$PATH"
set _OLD_VIRTUAL_PROMPT="$prompt"
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
set prompt = '(venv) '"$prompt"
setenv VIRTUAL_ENV_PROMPT '(venv) '
endif
alias pydoc python -m pydoc
rehash

69
venv/bin/activate.fish Normal file
View File

@@ -0,0 +1,69 @@
# This file must be used with "source <venv>/bin/activate.fish" *from fish*
# (https://fishshell.com/). You cannot run it directly.
function deactivate -d "Exit virtual environment and return to normal shell environment"
# reset old environment variables
if test -n "$_OLD_VIRTUAL_PATH"
set -gx PATH $_OLD_VIRTUAL_PATH
set -e _OLD_VIRTUAL_PATH
end
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
set -e _OLD_VIRTUAL_PYTHONHOME
end
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
set -e _OLD_FISH_PROMPT_OVERRIDE
# prevents error when using nested fish instances (Issue #93858)
if functions -q _old_fish_prompt
functions -e fish_prompt
functions -c _old_fish_prompt fish_prompt
functions -e _old_fish_prompt
end
end
set -e VIRTUAL_ENV
set -e VIRTUAL_ENV_PROMPT
if test "$argv[1]" != "nondestructive"
# Self-destruct!
functions -e deactivate
end
end
# Unset irrelevant variables.
deactivate nondestructive
set -gx VIRTUAL_ENV /home/van/project/jd_python/venv
set -gx _OLD_VIRTUAL_PATH $PATH
set -gx PATH "$VIRTUAL_ENV/"bin $PATH
# Unset PYTHONHOME if set.
if set -q PYTHONHOME
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
set -e PYTHONHOME
end
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
# fish uses a function instead of an env var to generate the prompt.
# Save the current fish_prompt function as the function _old_fish_prompt.
functions -c fish_prompt _old_fish_prompt
# With the original prompt function renamed, we can override with our own.
function fish_prompt
# Save the return status of the last command.
set -l old_status $status
# Output the venv prompt; color taken from the blue of the Python logo.
printf "%s%s%s" (set_color 4B8BBE) '(venv) ' (set_color normal)
# Restore the return status of the previous command.
echo "exit $old_status" | .
# Output the original/"old" prompt.
_old_fish_prompt
end
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
set -gx VIRTUAL_ENV_PROMPT '(venv) '
end

7
venv/bin/dp Normal file
View File

@@ -0,0 +1,7 @@
#!/home/van/project/jd_python/venv/bin/python3
import sys
from DrissionPage._functions.cli import main
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())

7
venv/bin/flask Normal file
View File

@@ -0,0 +1,7 @@
#!/home/van/project/jd_python/venv/bin/python3
import sys
from flask.cli import main
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())

7
venv/bin/normalizer Normal file
View File

@@ -0,0 +1,7 @@
#!/home/van/project/jd_python/venv/bin/python3
import sys
from charset_normalizer.cli import cli_detect
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(cli_detect())

8
venv/bin/pip Normal file
View File

@@ -0,0 +1,8 @@
#!/home/van/project/jd_python/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from pip._internal.cli.main import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

8
venv/bin/pip3 Normal file
View File

@@ -0,0 +1,8 @@
#!/home/van/project/jd_python/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from pip._internal.cli.main import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

8
venv/bin/pip3.12 Normal file
View File

@@ -0,0 +1,8 @@
#!/home/van/project/jd_python/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from pip._internal.cli.main import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

1
venv/bin/python Normal file
View File

@@ -0,0 +1 @@
python3

1
venv/bin/python3 Normal file
View File

@@ -0,0 +1 @@
/usr/bin/python3

1
venv/bin/python3.12 Normal file
View File

@@ -0,0 +1 @@
python3

7
venv/bin/tldextract Normal file
View File

@@ -0,0 +1,7 @@
#!/home/van/project/jd_python/venv/bin/python3
import sys
from tldextract.cli import main
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())

7
venv/bin/wsdump Normal file
View File

@@ -0,0 +1,7 @@
#!/home/van/project/jd_python/venv/bin/python3
import sys
from websocket._wsdump import main
if __name__ == '__main__':
if sys.argv[0].endswith('.exe'):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())

View File

@@ -0,0 +1,164 @@
/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
/* Greenlet object interface */
#ifndef Py_GREENLETOBJECT_H
#define Py_GREENLETOBJECT_H
#include <Python.h>
#ifdef __cplusplus
extern "C" {
#endif
/* This is deprecated and undocumented. It does not change. */
#define GREENLET_VERSION "1.0.0"
#ifndef GREENLET_MODULE
#define implementation_ptr_t void*
#endif
typedef struct _greenlet {
PyObject_HEAD
PyObject* weakreflist;
PyObject* dict;
implementation_ptr_t pimpl;
} PyGreenlet;
#define PyGreenlet_Check(op) (op && PyObject_TypeCheck(op, &PyGreenlet_Type))
/* C API functions */
/* Total number of symbols that are exported */
#define PyGreenlet_API_pointers 12
#define PyGreenlet_Type_NUM 0
#define PyExc_GreenletError_NUM 1
#define PyExc_GreenletExit_NUM 2
#define PyGreenlet_New_NUM 3
#define PyGreenlet_GetCurrent_NUM 4
#define PyGreenlet_Throw_NUM 5
#define PyGreenlet_Switch_NUM 6
#define PyGreenlet_SetParent_NUM 7
#define PyGreenlet_MAIN_NUM 8
#define PyGreenlet_STARTED_NUM 9
#define PyGreenlet_ACTIVE_NUM 10
#define PyGreenlet_GET_PARENT_NUM 11
#ifndef GREENLET_MODULE
/* This section is used by modules that uses the greenlet C API */
static void** _PyGreenlet_API = NULL;
# define PyGreenlet_Type \
(*(PyTypeObject*)_PyGreenlet_API[PyGreenlet_Type_NUM])
# define PyExc_GreenletError \
((PyObject*)_PyGreenlet_API[PyExc_GreenletError_NUM])
# define PyExc_GreenletExit \
((PyObject*)_PyGreenlet_API[PyExc_GreenletExit_NUM])
/*
* PyGreenlet_New(PyObject *args)
*
* greenlet.greenlet(run, parent=None)
*/
# define PyGreenlet_New \
(*(PyGreenlet * (*)(PyObject * run, PyGreenlet * parent)) \
_PyGreenlet_API[PyGreenlet_New_NUM])
/*
* PyGreenlet_GetCurrent(void)
*
* greenlet.getcurrent()
*/
# define PyGreenlet_GetCurrent \
(*(PyGreenlet * (*)(void)) _PyGreenlet_API[PyGreenlet_GetCurrent_NUM])
/*
* PyGreenlet_Throw(
* PyGreenlet *greenlet,
* PyObject *typ,
* PyObject *val,
* PyObject *tb)
*
* g.throw(...)
*/
# define PyGreenlet_Throw \
(*(PyObject * (*)(PyGreenlet * self, \
PyObject * typ, \
PyObject * val, \
PyObject * tb)) \
_PyGreenlet_API[PyGreenlet_Throw_NUM])
/*
* PyGreenlet_Switch(PyGreenlet *greenlet, PyObject *args)
*
* g.switch(*args, **kwargs)
*/
# define PyGreenlet_Switch \
(*(PyObject * \
(*)(PyGreenlet * greenlet, PyObject * args, PyObject * kwargs)) \
_PyGreenlet_API[PyGreenlet_Switch_NUM])
/*
* PyGreenlet_SetParent(PyObject *greenlet, PyObject *new_parent)
*
* g.parent = new_parent
*/
# define PyGreenlet_SetParent \
(*(int (*)(PyGreenlet * greenlet, PyGreenlet * nparent)) \
_PyGreenlet_API[PyGreenlet_SetParent_NUM])
/*
* PyGreenlet_GetParent(PyObject* greenlet)
*
* return greenlet.parent;
*
* This could return NULL even if there is no exception active.
* If it does not return NULL, you are responsible for decrementing the
* reference count.
*/
# define PyGreenlet_GetParent \
(*(PyGreenlet* (*)(PyGreenlet*)) \
_PyGreenlet_API[PyGreenlet_GET_PARENT_NUM])
/*
* deprecated, undocumented alias.
*/
# define PyGreenlet_GET_PARENT PyGreenlet_GetParent
# define PyGreenlet_MAIN \
(*(int (*)(PyGreenlet*)) \
_PyGreenlet_API[PyGreenlet_MAIN_NUM])
# define PyGreenlet_STARTED \
(*(int (*)(PyGreenlet*)) \
_PyGreenlet_API[PyGreenlet_STARTED_NUM])
# define PyGreenlet_ACTIVE \
(*(int (*)(PyGreenlet*)) \
_PyGreenlet_API[PyGreenlet_ACTIVE_NUM])
/* Macro that imports greenlet and initializes C API */
/* NOTE: This has actually moved to ``greenlet._greenlet._C_API``, but we
keep the older definition to be sure older code that might have a copy of
the header still works. */
# define PyGreenlet_Import() \
{ \
_PyGreenlet_API = (void**)PyCapsule_Import("greenlet._C_API", 0); \
}
#endif /* GREENLET_MODULE */
#ifdef __cplusplus
}
#endif
#endif /* !Py_GREENLETOBJECT_H */

1
venv/lib64 Normal file
View File

@@ -0,0 +1 @@
lib

5
venv/pyvenv.cfg Normal file
View File

@@ -0,0 +1,5 @@
home = /usr/bin
include-system-site-packages = false
version = 3.12.3
executable = /usr/bin/python3.12
command = /usr/bin/python3 -m venv /home/van/project/jd_python/venv