echopeak-models / fetch_licenses.py
mendelmakerpaul's picture
Replace Gemma 3 1B with Qwen2.5-1.5B q8; both models now Apache 2.0
48c7fd5 verified
Raw
History Blame Contribute Delete
4.5 kB
#!/usr/bin/env python3
"""Fetch the Gemma licence texts that must ship alongside redistributed weights.
Apache 2.0 has a canonical plaintext URL and is downloaded verbatim. The two Gemma
documents only exist as HTML pages, so their article body is extracted to text.
The extracted files are a convenience, not an authority: diff them against the live
pages before publishing a repo that relies on them. Re-run whenever Google updates
the terms.
Usage: python fetch_licenses.py
"""
import re
import sys
import urllib.request
try:
from bs4 import BeautifulSoup, NavigableString, Tag
except ImportError:
sys.exit("需要 beautifulsoup4:pip install beautifulsoup4")
PLAINTEXT = {
"LICENSE-apache-2.0.txt": "https://www.apache.org/licenses/LICENSE-2.0.txt",
}
# Both shipped models are Apache 2.0, so nothing here needs scraping today. Keep the entries
# commented rather than deleting the machinery: re-adding any Gemma 1/1.1/2/3/3n model brings
# these obligations straight back, and the extraction is fiddly enough to be worth preserving.
HTML_PAGES: dict[str, str] = {
# "LICENSE-gemma-terms.txt": "https://ai.google.dev/gemma/terms",
# "PROHIBITED_USE_POLICY.txt": "https://ai.google.dev/gemma/prohibited_use_policy",
}
BLOCK_TAGS = {"p", "li", "h1", "h2", "h3", "h4", "h5", "h6", "tr", "div", "section"}
HEADING_TAGS = {"h1", "h2", "h3", "h4", "h5", "h6"}
def get(url: str) -> bytes:
request = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
with urllib.request.urlopen(request, timeout=30) as response:
return response.read()
def render(node, out, depth=0):
"""Walk the article tree emitting one line per block element."""
if isinstance(node, NavigableString):
text = str(node).strip()
if text:
out.append(("inline", text))
return
if not isinstance(node, Tag):
return
if node.name in {"script", "style", "nav", "button"}:
return
if node.name in BLOCK_TAGS:
text = " ".join(node.get_text(" ", strip=True).split())
if text:
# Only emit leaf-ish blocks; container divs would duplicate their children.
has_block_child = any(
isinstance(c, Tag) and c.name in BLOCK_TAGS for c in node.children
)
if not has_block_child:
prefix = "- " if node.name == "li" else ""
kind = "heading" if node.name in HEADING_TAGS else "block"
out.append((kind, prefix + text))
return
for child in node.children:
render(child, out, depth + 1)
def tidy(text: str) -> str:
"""Undo spacing artefacts left by inline tags around defined terms."""
text = re.sub(r'"\s+(.*?)\s+"', r'"\1"', text)
return re.sub(r"\s+([.,;:])", r"\1", text)
def html_to_text(html: bytes) -> str:
soup = BeautifulSoup(html, "html.parser")
article = soup.find("article") or soup.body
if article is None:
raise SystemExit("找不到文章內容,頁面結構可能已改變")
out = []
render(article, out)
# Drop the site chrome (release banner, breadcrumbs) preceding the document title.
first_heading = next((i for i, (kind, _) in enumerate(out) if kind == "heading"), 0)
out = [(kind, tidy(text)) for kind, text in out[first_heading:]]
lines = []
for kind, text in out:
if kind == "heading":
lines.append("")
lines.append(text)
lines.append("=" * len(text))
else:
lines.append(text)
lines.append("")
# Collapse runs of blank lines.
result, blank = [], False
for line in lines:
if line.strip() == "":
if not blank:
result.append("")
blank = True
else:
result.append(line)
blank = False
return "\n".join(result).strip() + "\n"
def main():
for name, url in PLAINTEXT.items():
print(f"下載 {name} <- {url}")
with open(name, "wb") as handle:
handle.write(get(url))
for name, url in HTML_PAGES.items():
print(f"擷取 {name} <- {url}")
text = html_to_text(get(url))
with open(name, "w", encoding="utf-8") as handle:
handle.write(text)
print(f" {len(text.splitlines())} 行,請人工核對")
print("\n完成。上傳前務必與官方頁面對照一次擷取出來的兩份 Gemma 文件。")
if __name__ == "__main__":
main()