1,微信公众号的对话框界面是 Native 的,比如:
2,微信公众号的文章界面是 WebView 的,比如:
3,新版微信使用 xweb 内核,开启调试的方式是在手机微信内点击或扫码打开 http://debugxweb.qq.com/?inspector=true
,打开后将跳转到微信首页,跳转后表示已经开启调试。
4,微信公众号有两种链接:
https://mp.weixin.qq.com/s?__biz=MzUxOTc4NjEyMw==&mid=2247584542&idx=1&sn=4054522a69670ddaadfca49eefbb240f&chksm=f840047bac0bcf5b
4810295a163673b1e4a652394f79f08362093b62ad57878beab230321720&sessionid=0&scene=126&subscene=0&clicktime=1727119790&enterid=1727119790&ascene=3&fasttmpl_type=0&fasttmpl_fullversion=
7396431-zh_CN-zip&fasttmpl_flag=0&realreporttime=1727119790133&devicetype=android-34&version=28003334&nettype=WIFI&abtest_cookie=AAACAA%3D%3D&lang=zh_CN&session_us=gh_4985c77f7fde&
countrycode=US&exportkey=n_ChQIAhIQMyoHrDnW1YhiFF%2F%2FygOoBxLoAQIE97dBBAEAAAAAADGVA8JtIYgAAAAOpnltbLcz9gKNyK89dVj00ZGsqht7sW5eR3xRYshI81wW9ZfQU7cxRCQ3d4CueU7UXlzbAxDll9ZOLD9M8eVgB
HN%2BeswnbwLiPzzMKV2b%2BdfzjJJ%2BVMHpucPkhM10GuLP6OQi2AM0mZ%2BFgwO3uKEsomP2mGS0EO9wwl4iRJ0W355Av63cwqNvb8qTSdXRi0frDTuF0NkFmo%2FDL70K%2FZp5zhNBa8TYHdoahfj%2B6DCq%2FYTAQCbs4Id84kfIACokV%2FavFmzgLO%2Br6YEA4MQlrMhVGLo%3D&pass_ticket=71x2LtN8BW0zmA3uMTCWq1dzxCBbyjkYru7SEn%2BKsc476E3bnFoNFDdcyNvAcp0C&wx_header=3
/s/
开头的是永久链接,比如 https://mp.weixin.qq.com/s/cB__EysjFL7dJK7W4A7QIw
5,获取微信公众号文章链接的其他方式包括:
本文讲述如何通过安卓微信获取公众号文章链接。
6,如果想使用 Appium 调试混合安卓应用,那么构建应用时,必须在 android.webkit.WebView 元素上,将 setWebContentsDebuggingEnabled 属性设置为 true
。对于微信而言,执行第 3 步即可。如果想了解更多详情,请阅读 https://appium.github.io/appium.io/docs/en/writing-running-appium/web/hybrid/
。
7,在获取公众号文章链接之前,需要先关注公众号。
依次执行如下操作,以获取文章链接:
在将 Driver 的上下文切换到 WebView 时,可以通过 Appium 日志查看应用使用的 WebView 版本(与系统 WebView 实现的版本不一定相同),比如:
[13f965c5][Chromedriver@f092] Got response with status 200: {"value":{"capabilities":{"acceptInsecureCerts":false,"browserName":"chrome","browserVersion":"126.0.6478.188","chrome":{"chromedriverVersion":"126.0.6478.182 (5b5d8292ddf182f8b2096fa665b473b6317906d5-refs/branch-heads/6478@{#1776})"},"fedcm:accounts":true,"goog:chromeOptions":{"debuggerAddress":"localhost:60658"},"pageLoadStrategy":"normal","platformName":"android","proxy":{},"setWindowRect":false,"strictFileInteractability":false,"timeouts":{"implicit":0,"pageLoad":300000,"script":30000},"unhandledPromptBehavior":"dismiss and notify","webauthn:extension:credBlob":false,"webauthn:extension:largeBlob":false,"webauthn:extension:minPinLength":false,"webauthn:extension:prf":false,"webauthn:virtualAuthenticators":false},"sessionId":"5a9581a653cfeb1e67d1de673d54cf84"}}
需要下载与之匹配的 chromedriver(本文是 126.0.6478.X):
https://developer.chrome.com/docs/chromedriver/downloads/version-selection?hl=zh-cn
,了解如何下载相应版本的 chromedriverhttps://github.com/GoogleChromeLabs/chrome-for-testing#json-api-endpoints
,搜索 known-good-versions-with-downloads.json
,查找相应版本 chromedriver 的下载地址不要忘记执行第 1 节的第 3 步。
说明:
- 本示例代码只获取第一篇文章
import random
import time
from selenium.webdriver.remote.webelement import WebElement
from typing_extensions import Self, Optional, Any
import logging
from appium import webdriver
from appium.webdriver.common.appiumby import AppiumBy
from appium.webdriver.extensions.android.nativekey import AndroidKey
from appium.options.android import UiAutomator2Options
from selenium.webdriver.common.by import By
logging.basicConfig(
level=logging.INFO,
format='[%(asctime)s] [%(filename)s:%(lineno)d] %(levelname)s - %(message)s',
datefmt="%Y-%m-%d %H:%M:%S",
)
LOGGER: logging.Logger = logging.getLogger(__name__)
class Weixin:
def __init__(
self: Self,
*,
platform_version: str,
device_name: str,
server_url: str = "http://localhost:4723",
implicitly_wait: float = 8,
explicitly_wait: float = 4,
new_command_timeout: float = 3600 * 8,
udid: Optional[str] = None,
) -> None:
self._desired_caps: dict[str, Any] = {
"platformName": "Android",
"automationName": "UiAutomator2",
# 使用 Unicode 编码发送字符串
"unicodeKeyboard": True,
# 隐藏键盘
"resetKeyboard": True,
# 不重置 App
"noReset": True,
# 加速动态页面的速度
"settings[waitForIdleTimeout]": 0,
# 微信包名
"appPackage": "com.tencent.mm",
"appActivity": ".ui.LauncherUI",
# 安卓版本
"platformVersion": platform_version,
# 设备名称,最好唯一
"deviceName": device_name,
# 两条 Appium 命令间的最长时间间隔,单位是秒
"newCommandTimeout": new_command_timeout,
}
# deviceName 只能用于区分苹果设备;
# udid 用于区分安卓设备,可以通过 adb devices 获取
if udid:
self._desired_caps["udid"] = udid
# Appium 服务地址
self._server_url: str = server_url
self._implicitly_wait: float = implicitly_wait
self._explicitly_wait: float = explicitly_wait
def _enter_page(self: Self, search_word: str) -> None:
"""
进入公众号页面。
对于微信公众号,必须先使用一个 Driver 将文章页面放到最前面,然后再启动新 Driver,才能点击三个点或调试 WebView。
:param search_word: 公众号名称
"""
driver: webdriver.Remote = webdriver.Remote(
self._server_url,
options=UiAutomator2Options().load_capabilities(self._desired_caps),
)
driver.implicitly_wait(self._implicitly_wait)
# 激活 App 使其处于前台
driver.activate_app(self._desired_caps["appPackage"])
LOGGER.info("clicking 通讯录")
driver.find_element(By.XPATH, '(//android.widget.TextView[@text="通讯录"])[1]').click()
LOGGER.info("clicked 通讯录")
LOGGER.info("clicking 公众号")
driver.find_element(By.XPATH, '(//android.widget.TextView[@text="公众号"])[1]').click()
LOGGER.info("clicked 公众号")
LOGGER.info("clicking search logo")
driver.find_element(AppiumBy.ACCESSIBILITY_ID, '搜索').click()
LOGGER.info("clicked search logo")
LOGGER.info("inputting search word")
driver.find_element(AppiumBy.CLASS_NAME, 'android.widget.EditText').send_keys(search_word)
driver.press_keycode(AndroidKey.ENTER)
LOGGER.info("inputted search word")
LOGGER.info("clicking search word")
driver.find_element(By.XPATH, f'(//android.widget.TextView[@text="{search_word}"])[1]').click()
LOGGER.info("clicked search word")
LOGGER.info("clicking settings")
driver.find_element(By.XPATH, '//android.widget.ImageView[@content-desc="设置"]').click()
LOGGER.info("clicked settings")
# 点击第一篇文章的图片
LOGGER.info("clicking a blogpost")
driver.find_element(
By.XPATH,
'(//androidx.recyclerview.widget.RecyclerView)[1]//android.widget.ImageView[1]'
).click()
LOGGER.info("clicked the blogpost")
driver.quit()
def path_1(self: Self, search_word: str) -> str:
"""
方式 1
"""
self._enter_page(search_word)
driver: webdriver.Remote = webdriver.Remote(
self._server_url,
options=UiAutomator2Options().load_capabilities(self._desired_caps),
)
driver.implicitly_wait(self._implicitly_wait)
LOGGER.info("clicking 更多信息")
driver.find_element(By.XPATH, '//android.widget.ImageView[@content-desc="更多信息"]').click()
LOGGER.info("clicked 更多信息")
LOGGER.info("clicking 复制链接")
driver.find_element(By.XPATH, '//android.widget.TextView[@text="复制链接"]').click()
LOGGER.info("clicked 复制链接")
url: str = driver.get_clipboard_text()
driver.quit()
return url
def path_2(self: Self, search_word: str) -> str:
"""
方式 2
"""
# 向 Capabilities 中添加 chromedriver
self._desired_caps["chromedriverExecutable"] = \
"C:\\Users\\Lenovo\\Downloads\\chromedriver-win64\\chromedriver-win64\\chromedriver.exe"
self._desired_caps["showChromedriverLog"] = True
# 进入文章界面
self._enter_page(search_word)
driver: webdriver.Remote = webdriver.Remote(
self._server_url,
options=UiAutomator2Options().load_capabilities(self._desired_caps),
)
driver.implicitly_wait(self._implicitly_wait)
# 将上下文从 Native 切换到 WebView
switched_to: str = "WEBVIEW_com.tencent.mm"
for context in driver.contexts:
LOGGER.info("context - %s", context)
LOGGER.info("switching context to %s", switched_to)
driver.switch_to.context(switched_to)
LOGGER.info("switched context to %s", switched_to)
# 等待页面加载
time.sleep(random.randint(1, 4))
url: str = driver.current_url
# 获取、打印标题
title: WebElement = driver.find_element(By.XPATH, '//*[@id="activity-name"]')
LOGGER.info("title - %s", title.text)
driver.quit()
return url
def terminate(self: Self) -> None:
"""
终止 App
"""
driver: webdriver.Remote = webdriver.Remote(
self._server_url,
options=UiAutomator2Options().load_capabilities(self._desired_caps),
)
driver.implicitly_wait(self._implicitly_wait)
LOGGER.info("terminating %s app", self._desired_caps["appPackage"])
driver.terminate_app(self._desired_caps["appPackage"])
driver.quit()
def test() -> None:
# 需要先关注公众号
search_word: str = "Java基基"
wx: Weixin = Weixin(
device_name="redminote13",
platform_version="14",
)
try:
print("permanent url - ", wx.path_1(search_word))
finally:
wx.terminate()
try:
print("temp url - ", wx.path_2(search_word))
finally:
wx.terminate()
if __name__ == "__main__":
test()
输出示例:
permanent url - https://mp.weixin.qq.com/s/C4Bz5vgvb70V0syKld5wUw
temp url - https://mp.weixin.qq.com/s?__biz=MzUxOTc4NjEyMw==&mid=2247557678&idx=1&sn=f7f3aca74f402b8354eff50a19ed1454&chksm=f9f7f9cace8070dc228c190df6167f356a87647750baa615660a62e
d62559f2021b64eddeab0&scene=231&subscene=0&clicktime=1727124988&enterid=1727124988&sessionid=0&ascene=3&fasttmpl_type=0&fasttmpl_fullversion=7396431-zh_CN-zip&fasttmpl_flag=0&realr
eporttime=1727124988811&devicetype=android-34&version=28003334&nettype=WIFI&abtest_cookie=AAACAA%3D%3D&lang=zh_CN&session_us=gh_4985c77f7fde&countrycode=US&exportkey=n_ChQIAhIQQEkm
VZBXX3g3W2QzKLJEZRLoAQIE97dBBAEAAAAAAN2BJEbo%2BBIAAAAOpnltbLcz9gKNyK89dVj09wBwO%2FYhwUF3PcDjQg2DdD%2B0dk8MpneT%2BoB%2BwlPdbRqQisQFGqh7akAUUFFwJrPIi7V17dHLQfzwf4%2FaUOhtJLM8wHE0zHCQ
Y0spLdCD6B48lzTtMDgolr1LUxU7iI6Xa8Z1mhJ5wx2%2BtESq46XH36U2HOcnNskUVDFv14BlccQyiV0i3xpPansdjF4JbbmAvVuPibzu%2Fb238S6iqpgxSz8si5rASC%2BeWmqExopFKH%2BEFEHPaEi7je4vVnXAjT79sL8%3D&pass_ticket=4NpFFXxMKqi95fAFcRIhy%2FOFVjx0P0Qk4rUfByuSPPh0PhibTQm2024H9URLldSP&wx_header=3