Dockerfile 部署
This commit is contained in:
@@ -98,48 +98,60 @@ def fetch_issue_receipt_incremental():
|
|||||||
page = get_page(port=9222)
|
page = get_page(port=9222)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
log("INFO", f"正在回到主页起点: {HOME_URL}")
|
log("INFO", f"正在直接跳转到发料单明细页面...")
|
||||||
page.get(HOME_URL)
|
page.get("https://yunmes.tftykj.cn/WorkOrdersQuery")
|
||||||
page.wait.load_start()
|
page.wait.load_start()
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
|
|
||||||
menus = [
|
|
||||||
("第一层: 业务统计报表", 'xpath://*[@id="app"]/div/div[1]/div[1]/div[2]/div/div[1]/div/div[10]/div/p'),
|
|
||||||
("第二层: 生产业务报表(推测)", 'xpath:/html/body/div[7]/div/div[1]/div/div[9]/div/p'),
|
|
||||||
("第三层: 发料单报表", 'xpath:/html/body/div[8]/div/div[1]/div/div[6]/div/p')
|
|
||||||
]
|
|
||||||
|
|
||||||
log("INFO", "模拟点击左侧导航菜单...")
|
|
||||||
for name, xpath in menus:
|
|
||||||
ele = page.ele(xpath, timeout=5)
|
|
||||||
if ele:
|
|
||||||
try: ele.click()
|
|
||||||
except: page.run_js("arguments[0].click();", ele)
|
|
||||||
time.sleep(1.5)
|
|
||||||
else:
|
|
||||||
log("ERR", f"找不到菜单元素: {name}")
|
|
||||||
return
|
|
||||||
|
|
||||||
log("OK", "✅ 成功点开发料单报表界面!")
|
|
||||||
|
|
||||||
# 隐藏菜单
|
|
||||||
blank_xpath = 'xpath://*[@id="app"]/div/div[1]/div[2]/div[1]/div[2]/div[2]/div/div[1]/div'
|
|
||||||
blank_ele = page.ele(blank_xpath, timeout=3)
|
|
||||||
if blank_ele:
|
|
||||||
try: blank_ele.click()
|
|
||||||
except: page.run_js("arguments[0].click();", blank_ele)
|
|
||||||
time.sleep(0.5)
|
|
||||||
|
|
||||||
log("INFO", f"开启底层数据拦截网: {API_TARGET}")
|
log("INFO", f"开启底层数据拦截网: {API_TARGET}")
|
||||||
page.listen.start(API_TARGET)
|
page.listen.start(API_TARGET)
|
||||||
|
|
||||||
|
# 为了能够获取当月的数据,强制设置时间为当月第一天到最后一天,并清理其他条件
|
||||||
|
import datetime, calendar
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
first_day = datetime.date(now.year, now.month, 1).strftime('%Y-%m-%d')
|
||||||
|
last_day = datetime.date(now.year, now.month, calendar.monthrange(now.year, now.month)[1]).strftime('%Y-%m-%d')
|
||||||
|
|
||||||
|
log("INFO", f"正在自动设置查询时间范围: {first_day} 至 {last_day}")
|
||||||
|
|
||||||
|
page.run_js(f"""
|
||||||
|
try {{
|
||||||
|
var dates = document.querySelectorAll('.datebox-f, .datetimebox-f, .el-date-editor input');
|
||||||
|
if (dates.length >= 2) {{
|
||||||
|
dates[0].value = '{first_day}';
|
||||||
|
dates[1].value = '{last_day}';
|
||||||
|
dates[0].dispatchEvent(new Event('input', {{ bubbles: true }}));
|
||||||
|
dates[0].dispatchEvent(new Event('change', {{ bubbles: true }}));
|
||||||
|
dates[1].dispatchEvent(new Event('input', {{ bubbles: true }}));
|
||||||
|
dates[1].dispatchEvent(new Event('change', {{ bubbles: true }}));
|
||||||
|
}}
|
||||||
|
}} catch(e) {{ console.log(e); }}
|
||||||
|
""")
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
# 因为是直接打开 URL,数据通常不会自动加载,所以尝试点击查询按钮
|
||||||
packet = page.listen.wait(timeout=10)
|
packet = page.listen.wait(timeout=10)
|
||||||
if not packet:
|
if not packet:
|
||||||
query_btn_xpath = 'xpath://*[@id="app"]/div/div[1]/div[2]/div[2]/div[1]/div[1]/div/button[1]/span'
|
# 兼容多种查询按钮的查找方式
|
||||||
query_btn = page.ele(query_btn_xpath, timeout=3)
|
log("INFO", "尝试寻找并点击页面上的【查询】按钮...")
|
||||||
|
query_btn = page.ele('text=查询', timeout=3)
|
||||||
|
if not query_btn:
|
||||||
|
query_btn = page.ele('xpath://button[contains(., "查询")]', timeout=3)
|
||||||
|
|
||||||
if query_btn:
|
if query_btn:
|
||||||
try: query_btn.click()
|
try: query_btn.click()
|
||||||
except: page.run_js("arguments[0].click();", query_btn)
|
except: page.run_js("arguments[0].click();", query_btn)
|
||||||
|
else:
|
||||||
|
log("WARN", "常规选择器找不到查询按钮,尝试使用全局 JS 强行寻找...")
|
||||||
|
page.run_js("""
|
||||||
|
var btns = document.querySelectorAll('button, a, .l-btn, .el-button');
|
||||||
|
for(var i=0; i<btns.length; i++) {
|
||||||
|
if(btns[i].innerText && btns[i].innerText.indexOf('查询') !== -1) {
|
||||||
|
btns[i].click();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""")
|
||||||
packet = page.listen.wait(timeout=15)
|
packet = page.listen.wait(timeout=15)
|
||||||
|
|
||||||
if not packet:
|
if not packet:
|
||||||
@@ -191,15 +203,8 @@ def fetch_issue_receipt_incremental():
|
|||||||
log("INFO", f"🔍 正在检查本页第一条数据: 发料单 {first_item.get('workOrdersNumber')} 行号 {first_item.get('lineNumber')} 物料 {first_item.get('materialCode')}")
|
log("INFO", f"🔍 正在检查本页第一条数据: 发料单 {first_item.get('workOrdersNumber')} 行号 {first_item.get('lineNumber')} 物料 {first_item.get('materialCode')}")
|
||||||
|
|
||||||
for raw_item in items:
|
for raw_item in items:
|
||||||
# 1. 检查是否存在
|
# 1. 如果不存在,提取并插入
|
||||||
if item_exists(cursor, raw_item):
|
if not item_exists(cursor, raw_item):
|
||||||
# 发料单的新数据都在最前面。当我们遇到一条已经在数据库里的数据时,
|
|
||||||
# 说明这之前的数据都是新的,这之后的数据肯定都抓过了,直接停止。
|
|
||||||
log("INFO", f"🛑 在第 {current_page} 页发现本地已存在的记录 (发料单: {raw_item.get('workOrdersNumber')} 行号: {raw_item.get('lineNumber')} 物料: {raw_item.get('materialCode')}),增量扫描结束!")
|
|
||||||
should_stop = True
|
|
||||||
break
|
|
||||||
|
|
||||||
# 2. 如果不存在,提取并插入
|
|
||||||
item = _extract_fields(raw_item)
|
item = _extract_fields(raw_item)
|
||||||
|
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
@@ -232,12 +237,15 @@ def fetch_issue_receipt_incremental():
|
|||||||
conn.commit()
|
conn.commit()
|
||||||
log("OK", f"第 {current_page} 页处理完毕,成功插入 {inserted_this_page} 条新数据。")
|
log("OK", f"第 {current_page} 页处理完毕,成功插入 {inserted_this_page} 条新数据。")
|
||||||
|
|
||||||
if should_stop:
|
|
||||||
break
|
|
||||||
else:
|
else:
|
||||||
log("ERR", f"第 {current_page} 页数据结构异常,中止。")
|
log("ERR", f"第 {current_page} 页数据结构异常,中止。")
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# 如果当页没有新数据插入,说明已经追上了旧数据,停止抓取
|
||||||
|
if inserted_this_page == 0 and current_page > 1:
|
||||||
|
log("OK", "🎉 本页未发现任何新数据,说明增量部分已全部抓取完毕,停止翻页!")
|
||||||
|
break
|
||||||
|
|
||||||
# 如果没遇到旧数据,继续点击下一页
|
# 如果没遇到旧数据,继续点击下一页
|
||||||
delay = random.uniform(1.5, 3.5)
|
delay = random.uniform(1.5, 3.5)
|
||||||
log("INFO", f"⏳ 停顿 {delay:.2f} 秒后点击下一页...")
|
log("INFO", f"⏳ 停顿 {delay:.2f} 秒后点击下一页...")
|
||||||
@@ -245,6 +253,9 @@ def fetch_issue_receipt_incremental():
|
|||||||
|
|
||||||
next_btn = None
|
next_btn = None
|
||||||
for _ in range(3):
|
for _ in range(3):
|
||||||
|
# 优先使用 pagination-next,如果不行再尝试 btn-next
|
||||||
|
next_btn = page.ele('xpath://*[contains(@class, "pagination-next")]', timeout=3)
|
||||||
|
if not next_btn:
|
||||||
next_btn = page.ele('xpath://button[contains(@class, "btn-next")]', timeout=3)
|
next_btn = page.ele('xpath://button[contains(@class, "btn-next")]', timeout=3)
|
||||||
if next_btn:
|
if next_btn:
|
||||||
break
|
break
|
||||||
@@ -259,12 +270,24 @@ def fetch_issue_receipt_incremental():
|
|||||||
aria_disabled = next_btn.attr("aria-disabled")
|
aria_disabled = next_btn.attr("aria-disabled")
|
||||||
is_disabled_attr = next_btn.attr("disabled") is not None
|
is_disabled_attr = next_btn.attr("disabled") is not None
|
||||||
|
|
||||||
if "disabled" in class_str or is_disabled_attr or aria_disabled == "true":
|
# 检查父元素 <li> 是否被禁用
|
||||||
|
parent_class_str = ""
|
||||||
|
try:
|
||||||
|
parent_ele = next_btn.parent()
|
||||||
|
parent_class_str = str(parent_ele.attr("class"))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if "disabled" in class_str or "disabled" in parent_class_str or is_disabled_attr or aria_disabled == "true":
|
||||||
log("OK", "🏁 下一页按钮已被禁用,已经翻到最后一页。")
|
log("OK", "🏁 下一页按钮已被禁用,已经翻到最后一页。")
|
||||||
break
|
break
|
||||||
|
|
||||||
try: next_btn.click()
|
try:
|
||||||
except: page.run_js("arguments[0].click();", next_btn)
|
# 优先使用 JS 点击防止遮挡
|
||||||
|
page.run_js("arguments[0].click();", next_btn)
|
||||||
|
except Exception as e:
|
||||||
|
log("ERR", f"JS 点击失败: {e},尝试普通点击...")
|
||||||
|
next_btn.click()
|
||||||
|
|
||||||
packet = page.listen.wait(timeout=15)
|
packet = page.listen.wait(timeout=15)
|
||||||
if not packet:
|
if not packet:
|
||||||
|
|||||||
@@ -435,6 +435,44 @@ def sync_receipts():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({"success": False, "message": f"系统错误: {str(e)}"}), 500
|
return jsonify({"success": False, "message": f"系统错误: {str(e)}"}), 500
|
||||||
|
|
||||||
|
@app.route('/api/sync_issue_receipts', methods=['POST'])
|
||||||
|
def sync_issue_receipts():
|
||||||
|
"""触发后台运行发料单明细增量抓取脚本"""
|
||||||
|
global is_browser_busy
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if is_browser_busy:
|
||||||
|
return jsonify({
|
||||||
|
"success": False,
|
||||||
|
"message": f"系统忙碌:当前正在执行 '{current_task_name}',请稍后再试。"
|
||||||
|
}), 409
|
||||||
|
|
||||||
|
if str(BROWSER_LOGIN_DIR) not in sys.path:
|
||||||
|
sys.path.insert(0, str(BROWSER_LOGIN_DIR))
|
||||||
|
|
||||||
|
def run_issue_receipt_sync():
|
||||||
|
set_browser_busy("手动发料单明细增量同步")
|
||||||
|
try:
|
||||||
|
from fetch_issue_receipt_incremental import fetch_issue_receipt_incremental
|
||||||
|
fetch_issue_receipt_incremental()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"发料单明细增量同步失败: {e}")
|
||||||
|
finally:
|
||||||
|
release_browser()
|
||||||
|
|
||||||
|
try:
|
||||||
|
threading.Thread(target=run_issue_receipt_sync, daemon=True).start()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
"success": True,
|
||||||
|
"message": "发料单明细增量抓取任务已在后台启动!请观察运行日志。",
|
||||||
|
"logs": "任务已在后台运行..."
|
||||||
|
})
|
||||||
|
except ImportError:
|
||||||
|
return jsonify({"success": False, "message": "找不到发料单增量抓取脚本或导入失败"}), 404
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"success": False, "message": f"系统错误: {str(e)}"}), 500
|
||||||
|
|
||||||
@app.route('/api/sync_work_orders', methods=['POST'])
|
@app.route('/api/sync_work_orders', methods=['POST'])
|
||||||
def sync_work_orders():
|
def sync_work_orders():
|
||||||
"""触发后台运行生产工单增量抓取脚本"""
|
"""触发后台运行生产工单增量抓取脚本"""
|
||||||
|
|||||||
Reference in New Issue
Block a user