← 返回
未分类 中文

12306 Train Booking

12306-specific knowledge for booking train tickets via the Android app. Covers UC WebView virtual list behavior, proven booking flow, and common pitfalls. Us...
12306安卓版购票技巧,覆盖UC WebView虚拟列表行为、成熟订票流程及常见坑点。
openlittlebear openlittlebear 来源
未分类 clawhub v1.0.1 2 版本 100000 Key: 无需
★ 0
Stars
📥 269
下载
💾 1
安装
2
版本
#12306#android#booking#latest#train

概述

12306 Train Booking (Android)

12306-specific knowledge, workflows, and pitfalls. The app uses Alipay Nebula UC WebView for the train list and booking pages, which has unique automation challenges.

🚨 Before Any 12306 Action

Ensure the app is running and on the correct page:

# Start Appium bridge (if using bridge_daemon.py approach):
bash ~/.openclaw/workspace/skills/appium-android-adb/start_bridge.sh

# Or use uiautomator2 directly (more reliable for WebView):
python3 -c "import uiautomator2 as u2; d = u2.connect(); d.app_start('com.MobileTicket')"

⚠️ Critical: UC WebView Virtual List Behavior

The train list uses a virtual list inside the UC WebView. This has extreme implications:

What DOES work

MethodTargetReliability
-----------------------------
uiautomator2 text('预订').click()"预订" book buttons✅ Always works (proper persistent bounds)
uiautomator2 text('查询车票').click()Native query button✅ Always works
uiautomator2 text('选择乘车人').click()Native passenger selector✅ Always works
uiautomator2.click(x, y) on native viewsDate tabs, bottom buttons✅ Always works
Screenshot + OCR + ADB tapAny visible element✅ Works with good OCR
Appium element.click() on "预订" buttons"预订" with proper bounds✅ Works

What DOES NOT work

MethodTargetWhy
---------------------
Clicking collapsed train name textTrain entry at y=407 or y=2255 (h=6)Virtual list collapses ALL non-current items
ADB input tapAny UC WebView contentUC WebView filters synthetic touch events
W3C Actions swipeUC WebView scrollTouch events filtered
mobile: scrollGestureUC WebView elementReturns False consistently
d.textContains('G 7 0 0 4').click()Train name in collapsed zoneMultiple trains share collapsed bounds
tap_bounds on collapsed elementTrain name at y=2255 with h=6Event goes to WebView's virtual position, not visual

Key Insight: "预订" Buttons Have Proper Bounds

The ONLY reliable click targets in the UC WebView train list are the "预订" action buttons. Unlike the text content (train name, times, seat info) which gets collapsed by the virtual list, the "预订" button is:

  • Always rendered with proper bounds (h=87px)
  • Clickable via accessibility service
  • Positioned consistently ~195px below each train card's top
  • Unique per train card (find the one nearest your target train's y-position)

Booking Flow

Step 1: Home Page — Search Trains

dump the page → check for "查询车票" button
If on MainActivity:
  → tap "查询车票" (native button, always works)
  → wait for train list to load (H5Activity appears)
If on H5Activity (already on train list):
  → proceed to Step 2

Step 2: Find Target Train's "预订" Button

DO NOT try to click the train name or text content. Instead:

  1. Scroll the list until you see trains near your target departure time:

```python

d.swipe_ext('up', scale=0.3) # scroll forward

d.swipe_ext('down', scale=0.3) # scroll backward

```

  1. Check for G7004 in the DOM (even collapsed):

```python

xml = d.dump_hierarchy()

if 'G 7 0 0 4' in xml:

# G7004 is in the list — look for its "预订" button

pass

```

  1. Find the "预订" button associated with your train. "预订" buttons appear at regular y-intervals (~315px apart). The one closest to your target train's y-position belongs to that train:

```python

# Get all "预订" buttons with positions

buttons = d(text='预订')

for btn in buttons:

pos = btn.info['bounds']

y_center = (pos['top'] + pos['bottom']) // 2

print(f"预订 at y={y_center}")

# G7004 at y=1793 → the "预订" at y=1988 is its book button

```

  1. Click "预订":

```python

d(text='预订').click() # clicks first one

# OR click specific one by position:

for btn in d(text='预订'):

if 1900 < btn.info['bounds']['top'] < 2100:

btn.click()

break

```

Step 2b: OCR-Based Fallback (if DOM is unreliable)

If the WebView's DOM is too collapsed to find the right "预订":

import subprocess, pytesseract
from PIL import Image

# Take screenshot
subprocess.run(['adb', 'shell', 'screencap', '-p', '/sdcard/screen.png'], timeout=10)
subprocess.run(['adb', 'pull', '/sdcard/screen.png', '/tmp/screen.png'], timeout=10)

# OCR to find departure time
data = pytesseract.image_to_data(Image.open('/tmp/screen.png'),
    lang='chi_sim+eng', output_type=pytesseract.Output.DICT, config='--psm 6')

# Find "08:00" departure text on LEFT side (x < 300)
for i in range(len(data['text'])):
    if '08:00' in data['text'][i] and int(data['left'][i]) < 300:
        y = data['top'][i]
        # Tap "预订" position (~195px below departure time)
        import subprocess
        subprocess.run(['adb', 'shell', 'input', 'tap', '939', str(y + 195)])
        break

Step 3: Quiet Carriage Dialog (if appears)

After clicking "预订", a dialog may appear:

  • "选择该车次二等座可能候补到静音车厢席位,是否继续选择?" → tap "继续" or "选择"
  • "温馨提示" with quiet carriage rules → tap "选择" (bottom-right button)

Use OCR to find the confirm button position, then ADB tap it.

Step 4: Select Passenger

On order page, verify:
  → G7004 train info displayed
  → 二等座 selected (default)
  → Price ¥41 shown

If no passenger selected:
  → tap "选择乘车人" (native, works)
  → tap passenger name (e.g. "熊子慧")
  → tap "确认" (bottom button)

Step 5: Submit Order

tap "提交订单" button
  → verify: order submission confirmation
  → if "温馨提示" with "车票信息已过期" → go back, re-search
  → if "立即支付" → proceed to payment

Step 6: Payment (manual)

"立即支付" or "去支付" appears → user handles Alipay/WeChat

Train List Navigation (Scrolling)

The UC WebView's virtual list has three zones:

  • Top collapsed (y=407, h=6): Items scrolled past the top
  • Proper viewport (y=500-2200, h=60-130): Currently visible items with real bounds
  • Bottom collapsed (y=2255, h=6): Items below the viewport

Each scroll (scale=0.3) advances the list by approximately 2 train cards (~30-60 min of departures). To reach a specific train:

  1. Start from the earliest visible time
  2. Calculate approximate scrolls needed: (target_hour - current_hour) * 60 / 15 (each scroll ≈ 15 min of departures)
  3. Scroll, then check for target text in DOM (even collapsed)
  4. Once target text appears in DOM, look for its "预订" button

Decision Tree

dump / check current page:
  ├─ package != "com.MobileTicket"
  │   → app not running, start it
  ├─ ".ui.activity.MainActivity" in activity
  │   → HOME PAGE
  │   → verify stations are set → tap "查询车票"
  ├─ "H5Activity" in activity
  │   ├─ "提交订单" in text → ORDER CONFIRMATION
  │   │   → select passenger if needed → submit
  │   ├─ "预订" in text → TRAIN LIST (with "预订" buttons)
  │   │   → find target train → click its "预订" button
  │   ├─ "次列车" in text → TRAIN LIST (loading)
  │   │   → scroll until target appears → find "预订"
  │   └─ "立即支付" in text → PAYMENT
  │       → user handles payment
  └─ anything else → not 12306, investigate

Common Pitfalls

  1. NEVER click collapsed train names — they all share the same y=407 or y=2255 bounds. Click the "预订" button instead.
  2. "预订" buttons have proper bounds — they're the only reliable WebView targets.
  3. Screen may auto-lock — keep device awake: adb shell svc power stayon true
  4. Session conflicts — Appium and uiautomator2 can't run simultaneously (both need AccessibilityService).
  5. Page may time out — if no action for ~5 min, the list might need re-querying.
  6. Date tabs are native — tapping "05月31日 周日" at bounds [189,257][312,383] always works.
  7. Train numbers have spaces — search for G 7 0 0 4 not G7004.

版本历史

共 2 个版本

  • v1.0.1 当前
    2026-06-01 21:19
  • v1.0.0
    2026-05-28 13:41

安全检测

腾讯云安全 (Keen)

队列中

腾讯云安全 (Sanbu)

队列中

🔗 相关推荐

life-service

Weather

steipete
获取当前天气和预报(无需API密钥)
★ 458 📥 229,525
life-service

Sonoscli

steipete
控制Sonos音箱(发现/状态/播放/音量/分组)
★ 56 📥 85,241
life-service

Caldav Calendar

asleep123
使用 vdirsyncer + khal 同步并查询 CalDAV 日历(iCloud、Google、Fastmail、Nextcloud 等)。适用于 Linux。
★ 244 📥 30,684