#!/usr/bin/env python3 """ youdao_share_fetch.py - fetch Youdao Note share content (text + images) Usage: python3 youdao_share_fetch.py [output_dir] Examples: python3 youdao_share_fetch.py https://share.note.youdao.com/s/DeSF10So /tmp/out python3 youdao_share_fetch.py d18bf6b03b7b97656ca8cec07eb31442 /tmp/out """ import sys import os import re import json import html import urllib.request import urllib.parse UA = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'} def get_note_id(url): """Extract note id from share url (short link or full share url).""" m = re.search(r'[?&]id=([0-9a-fA-F]{32})', url) if m: return m.group(1) # short link -> follow redirect and re-extract req = urllib.request.Request(url, headers=UA) resp = urllib.request.urlopen(req, timeout=20) final = resp.geturl() m = re.search(r'[?&]id=([0-9a-fA-F]{32})', final) if m: return m.group(1) return None def fetch_note(note_id, unlogin_id='anonymous'): """Fetch note JSON via the public read API.""" url = ('https://note.youdao.com/yws/api/note/' + note_id + '?sev=j1&editorType=1&unloginId=' + unlogin_id + '&ignoreOwnPassword=true') req = urllib.request.Request(url, headers=UA) resp = urllib.request.urlopen(req, timeout=30) return json.loads(resp.read().decode('utf-8')) def parse_content(xml_content): """Extract text nodes and image source urls from note XML.""" texts = [html.unescape(t) for t in re.findall(r'([^<]*)', xml_content) if t.strip()] imgs = re.findall(r'([^<]+)', xml_content) return texts, imgs def download(url, outpath): """Download a resource file to outpath, return byte count.""" req = urllib.request.Request(url, headers=UA) data = urllib.request.urlopen(req, timeout=30).read() with open(outpath, 'wb') as f: f.write(data) return len(data) def main(): if len(sys.argv) < 2: print(__doc__) sys.exit(1) target = sys.argv[1] outdir = sys.argv[2] if len(sys.argv) > 2 else '.' if re.fullmatch(r'[0-9a-fA-F]{32}', target): note_id = target else: note_id = get_note_id(target) if not note_id: print('ERROR: cannot resolve note id from:', target) sys.exit(1) print('noteId:', note_id) os.makedirs(outdir, exist_ok=True) data = fetch_note(note_id) print('title:', data.get('tl')) texts, imgs = parse_content(data.get('content', '')) for t in texts: print('TEXT:', t) print('images:', len(imgs)) img_paths = [] for i, u in enumerate(imgs): ext = os.path.splitext(urllib.parse.urlparse(u).path)[1] or '.png' p = os.path.join(outdir, 'img_%02d%s' % (i, ext)) try: n = download(u, p) img_paths.append(p) print(' saved', p, '(%d bytes)' % n) except Exception as e: print(' FAIL img %d: %s' % (i, e)) meta = {'noteId': note_id, 'title': data.get('tl'), 'texts': texts, 'images': img_paths} meta_path = os.path.join(outdir, 'meta.json') with open(meta_path, 'w', encoding='utf-8') as f: json.dump(meta, f, ensure_ascii=False, indent=2) print('done. meta ->', meta_path) if __name__ == '__main__': main()