File size: 4,500 Bytes
a023f63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48c7fd5
 
 
 
 
 
a023f63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#!/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()