How to Automate Publishing to CSDN and WeChat MP Using Playwright (When APIs Fail)
Overview Today's focus was on automating article publishing to CSDN and WeChat MP (微信公众号) using Playwright, after CSDN deprecated its public Open API. Key achievements include: injecting Markdown content into CSDN's dynamic editor, handling title input quirks, implementing QR code login for WeChat MP, updating the Dev.to API publisher, and consolidating platform configs into a single YAML file. We also fixed session log capture after a Claude Code update changed the log file path. Problems and Solutions 1. CSDN Open API Deprecation → Browser Automation Background : In early 2026, CSDN silently shut down its public Open API. All endpoints returned 404/403. We needed a fallback to keep publishing to China's largest developer platform. Solution : Use Playwright to simulate a real user login and article creation. The approach: Launch a headless Chromium browser. Navigate to CSDN's login page. Perform one-time manual login via QR code. Serialize cookies to csdn_cookies.json . On subsequent runs, load the cookies and skip login. Go to the editor, inject Markdown content via DOM manipulation, fill the title, and click publish. Code snippet : import asyncio from playwright.async_api import async_playwright async def publish_to_csdn ( title : str , content_md : str ): async with async_playwright () as p : browser = await p . chromium . launch ( headless = True ) context = await browser . new_context ( storage_state = " csdn_cookies.json " if exists else None ) page = await context . new_page () await page . goto ( " https://mp.csdn.net/mp_blog/creation/editor " ) # Inject content await page . evaluate ( f ''' () => {{ const editor = document.querySelector( ' .editor-content ' ); if (editor) {{ editor.innerHTML = ` { escaped_content } `; editor.dispatchEvent(new Event( ' input ' , {{ bubbles: true }})); }} }} ''' ) # Fill title await page . fill ( ' #title-input ' , title ) await page . click ( ' button:has-text( " 发布 " ) ' ) await page . wait_for_url ( " **/mp_blog/manage/ar