-
Notifications
You must be signed in to change notification settings - Fork 117
chore: revert README to previous version #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,139 @@ | ||||||||||||||||||||||||
| #!/usr/bin/env python3 | ||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||
| Fetch latest posts from https://mysql.taobao.org/monthly/ (or a mirror) and | ||||||||||||||||||||||||
| update README.md with new entries grouped by database categories. The script | ||||||||||||||||||||||||
| parses month pages and appends unseen articles to the corresponding sections in | ||||||||||||||||||||||||
| README.md. If the default site is unreachable, set the ``MONTHLY_BASE_URL`` | ||||||||||||||||||||||||
| environment variable to point to an accessible mirror. | ||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||
| import re | ||||||||||||||||||||||||
| import sys | ||||||||||||||||||||||||
| from collections import defaultdict | ||||||||||||||||||||||||
| from pathlib import Path | ||||||||||||||||||||||||
| from html.parser import HTMLParser | ||||||||||||||||||||||||
| import urllib.request | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| BASE_URL = os.environ.get("MONTHLY_BASE_URL", "https://mysql.taobao.org/monthly") | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| def fetch(url: str) -> str: | ||||||||||||||||||||||||
| """Fetch URL and return decoded text.""" | ||||||||||||||||||||||||
| req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"}) | ||||||||||||||||||||||||
| proxy = os.environ.get("MONTHLY_PROXY") | ||||||||||||||||||||||||
| if proxy: | ||||||||||||||||||||||||
| handler = urllib.request.ProxyHandler({"http": proxy, "https": proxy}) | ||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||
| handler = urllib.request.ProxyHandler({}) # disable env proxies | ||||||||||||||||||||||||
| opener = urllib.request.build_opener(handler) | ||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||
| with opener.open(req, timeout=10) as resp: # pragma: no cover - network I/O | ||||||||||||||||||||||||
| return resp.read().decode("utf-8", errors="ignore") | ||||||||||||||||||||||||
| except Exception as exc: # pragma: no cover - network I/O | ||||||||||||||||||||||||
| raise RuntimeError(f"failed to fetch {url}: {exc}") | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| def get_months(html: str): | ||||||||||||||||||||||||
| """Extract all (year, month) tuples from index html.""" | ||||||||||||||||||||||||
| pattern = re.compile(r"/monthly/(\d{4})/(\d{2})/") | ||||||||||||||||||||||||
| months = {(int(y), int(m)) for y, m in pattern.findall(html)} | ||||||||||||||||||||||||
| return sorted(months) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| class MonthParser(HTMLParser): | ||||||||||||||||||||||||
| """Parse monthly page into articles grouped by category.""" | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| def __init__(self): | ||||||||||||||||||||||||
| super().__init__() | ||||||||||||||||||||||||
| self.category = None | ||||||||||||||||||||||||
| self.in_h2 = False | ||||||||||||||||||||||||
| self.in_li = False | ||||||||||||||||||||||||
| self.link = None | ||||||||||||||||||||||||
| self.text_parts = [] | ||||||||||||||||||||||||
| self.articles = defaultdict(list) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| def handle_starttag(self, tag, attrs): | ||||||||||||||||||||||||
| if tag == "h2": | ||||||||||||||||||||||||
| self.in_h2 = True | ||||||||||||||||||||||||
| elif tag == "li": | ||||||||||||||||||||||||
| self.in_li = True | ||||||||||||||||||||||||
| self.link = None | ||||||||||||||||||||||||
| self.text_parts = [] | ||||||||||||||||||||||||
| elif tag == "a" and self.in_li: | ||||||||||||||||||||||||
| attrs = dict(attrs) | ||||||||||||||||||||||||
| self.link = attrs.get("href") | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| def handle_endtag(self, tag): | ||||||||||||||||||||||||
| if tag == "h2": | ||||||||||||||||||||||||
| self.in_h2 = False | ||||||||||||||||||||||||
| elif tag == "li": | ||||||||||||||||||||||||
| if self.category and self.link and self.text_parts: | ||||||||||||||||||||||||
| text = "".join(self.text_parts).strip() | ||||||||||||||||||||||||
| m = re.match(r"\[(.*?)\]\s*(.*)", text) | ||||||||||||||||||||||||
| if m: | ||||||||||||||||||||||||
| typ, title = m.groups() | ||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||
| parts = text.split(None, 1) | ||||||||||||||||||||||||
| typ = parts[0] if len(parts) == 2 else "" | ||||||||||||||||||||||||
| title = parts[-1] | ||||||||||||||||||||||||
| self.articles[self.category].append((typ, title, self.link)) | ||||||||||||||||||||||||
| self.in_li = False | ||||||||||||||||||||||||
| self.link = None | ||||||||||||||||||||||||
| self.text_parts = [] | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| def handle_data(self, data): | ||||||||||||||||||||||||
| if self.in_h2: | ||||||||||||||||||||||||
| self.category = data.strip() | ||||||||||||||||||||||||
| elif self.in_li: | ||||||||||||||||||||||||
| self.text_parts.append(data) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| def parse_month(year: int, month: int): | ||||||||||||||||||||||||
| html = fetch(f"{BASE_URL}/{year:04d}/{month:02d}/") | ||||||||||||||||||||||||
| parser = MonthParser() | ||||||||||||||||||||||||
| parser.feed(html) | ||||||||||||||||||||||||
| return parser.articles | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| def update_readme(new_articles): | ||||||||||||||||||||||||
| readme_path = Path(__file__).resolve().parent.parent / "README.md" | ||||||||||||||||||||||||
| content = readme_path.read_text(encoding="utf-8") | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| for category, items in new_articles.items(): | ||||||||||||||||||||||||
| if not items: | ||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||
| table_header = f"# {category}\n| 分类 | 标题 |\n|---|---|" | ||||||||||||||||||||||||
| if table_header not in content: | ||||||||||||||||||||||||
| # skip categories not present | ||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||
| lines = [f"| {typ} | [{title}]({link}) |" for typ, title, link in items] | ||||||||||||||||||||||||
| content = content.replace(table_header, table_header + "\n" + "\n".join(lines)) | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
| content = content.replace(table_header, table_header + "\n" + "\n".join(lines)) | |
| # Use regex to match the table header and following table rows | |
| pattern = re.compile( | |
| re.escape(table_header) + r"(?:\n(?:\|.*\|))*", | |
| re.MULTILINE | |
| ) | |
| replacement = table_header + "\n" + "\n".join(lines) | |
| content, count = pattern.subn(replacement, content, count=1) | |
| if count == 0: | |
| # fallback: skip if not matched (should not happen due to earlier check) | |
| continue |
Copilot
AI
Aug 15, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using string containment check on the entire README content for each URL is inefficient. Consider extracting existing URLs once at the beginning using regex and storing them in a set for O(1) lookup instead of O(n) string searches.
| if url in existing: | |
| # Extract all URLs from the README content and store in a set for O(1) lookup | |
| existing_urls = set(re.findall(r"https?://[^\s\)]+", existing)) | |
| updates = defaultdict(list) | |
| for y, m in months: | |
| url = f"{BASE_URL}/{y:04d}/{m:02d}/" | |
| if url in existing_urls: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The hardcoded timeout value of 10 seconds should be configurable. Consider adding a TIMEOUT environment variable or making it a module-level constant to allow customization for different network conditions.