wikiharvest bundle for the Azure containers (public inputs: the undated-marriage pool + sitelinks)
Browse files- wikiharvest/found.csv.gz +3 -0
- wikiharvest/wiki_start_harvest.py +97 -19
wikiharvest/found.csv.gz
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9ceffb7310338057cfe71a4ae1e2c98d2c485d81cdef906532a801f457a38d64
|
| 3 |
+
size 20
|
wikiharvest/wiki_start_harvest.py
CHANGED
|
@@ -139,8 +139,55 @@ def sitelinks():
|
|
| 139 |
|
| 140 |
|
| 141 |
# ── phase C: the wikitext harvest ────────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
def _find_dates(lang, text, spouse_names):
|
| 143 |
-
"""(start_iso, prec, snippet) for the marriage to this spouse
|
| 144 |
esc = [re.escape(n) for n in spouse_names if n and len(n) > 2]
|
| 145 |
if not esc:
|
| 146 |
return None
|
|
@@ -151,32 +198,63 @@ def _find_dates(lang, text, spouse_names):
|
|
| 151 |
body = m.group(1)
|
| 152 |
if not re.search(name_re, body, re.I):
|
| 153 |
continue
|
| 154 |
-
|
| 155 |
-
if
|
| 156 |
-
return
|
| 157 |
-
MONTHS_EN = {m: i + 1 for i, m in enumerate(("january", "february", "march", "april", "may", "june", "july", "august", "september", "october", "november", "december"))}
|
| 158 |
-
d1 = re.search(r"(\d{1,2})\s+([A-Za-z]+)\s+" + YEAR, body) or re.search(r"([A-Za-z]+)\s+(\d{1,2}),\s*" + YEAR, body)
|
| 159 |
-
if d1:
|
| 160 |
-
g = d1.groups(); mon = MONTHS_EN.get((g[1] if g[0].isdigit() else g[0]).lower()); day = int(g[0] if g[0].isdigit() else g[1]); yr4 = g[2]
|
| 161 |
-
if mon:
|
| 162 |
-
return f"{yr4}-{mon:02d}-{day:02d}", 11, body[:160]
|
| 163 |
yr = re.search(YEAR, body)
|
| 164 |
if yr:
|
| 165 |
-
return yr.group(0) + "-00-00", 9, body[:160]
|
| 166 |
# 2. the spouse parameter's value line: |spouse = [[Name]] (m. 1903; div 1919) / ⚭ 1903 / с 1903
|
| 167 |
for par in SPOUSE_PARAMS.get(lang, []) + SPOUSE_PARAMS.get("en", []):
|
| 168 |
for m in re.finditer(r"\|\s*" + re.escape(par) + r"\s*=([^\n]*?" + name_re + r"[^\n]*)", text, re.I):
|
| 169 |
line = m.group(1)
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 173 |
# 3. a parenthetical right after the spouse's name, REQUIRING a marriage marker — "(1848–1919)" is a lifespan
|
| 174 |
-
MARK = {"en": r"m\.|marr", "de": r"⚭|verh", "fr": r"mari|ép", "es": r"matr|casad", "it": r"spos", "ru": r"брак|с\s", "uk": r"шлюб|з\s", "pl": r"ślub|od\s", "pt": r"casad", "nl": r"getr", "sv": r"gift", "da": r"gift", "fi": r"avio", "cs": r"sňat|od\s", "hu": r"házas", "tr": r"evl", "fa": r"ازدواج", "ar": r"تزوج|زواج", "ja": r"結婚", "zh": r"结婚|結婚", "hy": r"ամուսն"}
|
| 175 |
-
mark = MARK.get(lang, r"⚭|m\.")
|
| 176 |
m = re.search(name_re + r"[^\n(]{0,40}\((?:[^)]{0,40}?(?:" + mark + r")[^)]{0,40}?)" + YEAR + r"[^)]*\)", text, re.I)
|
| 177 |
if m:
|
| 178 |
yr = re.search(YEAR, m.group(0))
|
| 179 |
-
return yr.group(0) + "-00-00", 9, m.group(0)[:160]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
return None
|
| 181 |
|
| 182 |
|
|
@@ -217,7 +295,7 @@ def harvest(langs=None):
|
|
| 217 |
continue
|
| 218 |
hit = _find_dates(lang, text, names)
|
| 219 |
if hit:
|
| 220 |
-
st, prec, snip = hit; sy = int(st[:4])
|
| 221 |
by = [int(r[k][:4]) for k in ("adob", "bdob") if r[k][:4].isdigit()]
|
| 222 |
ey = [int(r[k][:4]) for k in ("end", "adeath", "bdeath") if r[k][:4].isdigit()]
|
| 223 |
# a wedding before either 14th birthday, or after the marriage's own end, is a misread — dropped
|
|
@@ -225,7 +303,7 @@ def harvest(langs=None):
|
|
| 225 |
continue
|
| 226 |
if ey and sy > min(ey):
|
| 227 |
continue
|
| 228 |
-
w.writerow([r["a"], r["b"], st, prec, lang, subj, snip.replace("\n", " ")]); got += 1
|
| 229 |
if i % 400 == 0 and i:
|
| 230 |
f.flush(); log(f" {lang}: {i:,}/{len(jobs):,} read · {got:,} dates found")
|
| 231 |
time.sleep(0.6)
|
|
|
|
| 139 |
|
| 140 |
|
| 141 |
# ── phase C: the wikitext harvest ────────────────────────────────────────────────────────────────────────────────
|
| 142 |
+
MONTHS = {
|
| 143 |
+
"en": "january february march april may june july august september october november december",
|
| 144 |
+
"de": "januar februar märz april mai juni juli august september oktober november dezember",
|
| 145 |
+
"fr": "janvier février mars avril mai juin juillet août septembre octobre novembre décembre",
|
| 146 |
+
"es": "enero febrero marzo abril mayo junio julio agosto septiembre octubre noviembre diciembre",
|
| 147 |
+
"it": "gennaio febbraio marzo aprile maggio giugno luglio agosto settembre ottobre novembre dicembre",
|
| 148 |
+
"pt": "janeiro fevereiro março abril maio junho julho agosto setembro outubro novembro dezembro",
|
| 149 |
+
"nl": "januari februari maart april mei juni juli augustus september oktober november december",
|
| 150 |
+
"sv": "januari februari mars april maj juni juli augusti september oktober november december",
|
| 151 |
+
"da": "januar februar marts april maj juni juli august september oktober november december",
|
| 152 |
+
"fi": "tammikuuta helmikuuta maaliskuuta huhtikuuta toukokuuta kesäkuuta heinäkuuta elokuuta syyskuuta lokakuuta marraskuuta joulukuuta",
|
| 153 |
+
"pl": "stycznia lutego marca kwietnia maja czerwca lipca sierpnia września października listopada grudnia",
|
| 154 |
+
"cs": "ledna února března dubna května června července srpna září října listopadu prosince",
|
| 155 |
+
"ru": "января февраля марта апреля мая июня июля августа сентября октября ноября декабря",
|
| 156 |
+
"uk": "січня лютого березня квітня травня червня липня серпня вересня жовтня листопада грудня",
|
| 157 |
+
}
|
| 158 |
+
_MTAB = {lang: {m: i + 1 for i, m in enumerate(v.split())} for lang, v in MONTHS.items()}
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
def _full_date(lang, snippet):
|
| 162 |
+
"""A day-precision ISO date inside the snippet, or None: ISO, '11 March 1902' (15 languages' month names,
|
| 163 |
+
prefix-matched), 'March 11, 1902', ja/zh 1902年3月11日."""
|
| 164 |
+
iso = re.search(YEAR + r"-(\d\d)-(\d\d)", snippet)
|
| 165 |
+
if iso:
|
| 166 |
+
return iso.group(0)
|
| 167 |
+
m = re.search(YEAR + r"年(\d{1,2})月(\d{1,2})日", snippet)
|
| 168 |
+
if m:
|
| 169 |
+
return f"{m.group(1)}-{int(m.group(2)):02d}-{int(m.group(3)):02d}"
|
| 170 |
+
tab = _MTAB.get(lang, {}); tab_en = _MTAB["en"]
|
| 171 |
+
m = re.search(r"(\d{1,2})\.?\s+([^\W\d_]+),?\s+" + YEAR, snippet, re.U) or re.search(r"([^\W\d_]+)\s+(\d{1,2}),?\s+" + YEAR, snippet, re.U)
|
| 172 |
+
if m:
|
| 173 |
+
g = m.groups(); word = (g[1] if g[0].isdigit() else g[0]).lower(); day = int(g[0] if g[0].isdigit() else g[1]); yr = g[2]
|
| 174 |
+
for t in (tab, tab_en):
|
| 175 |
+
for name, mon in t.items():
|
| 176 |
+
if word.startswith(name[:4]) and 1 <= day <= 31:
|
| 177 |
+
return f"{yr}-{mon:02d}-{day:02d}"
|
| 178 |
+
return None
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
def _end_year(snippet, start_year):
|
| 182 |
+
"""The marriage's END year inside the same construct: any later year in the snippet (a {{marriage}} second
|
| 183 |
+
argument, 'div. 1911', 'ended 1911'); None when the construct names none."""
|
| 184 |
+
ys = [int(y) for y in re.findall(YEAR, snippet)]
|
| 185 |
+
later = [y for y in ys if y > start_year]
|
| 186 |
+
return min(later) if later else None
|
| 187 |
+
|
| 188 |
+
|
| 189 |
def _find_dates(lang, text, spouse_names):
|
| 190 |
+
"""(start_iso, prec, snippet, end_year) for the marriage to this spouse, or None. prec 11 day / 9 year."""
|
| 191 |
esc = [re.escape(n) for n in spouse_names if n and len(n) > 2]
|
| 192 |
if not esc:
|
| 193 |
return None
|
|
|
|
| 198 |
body = m.group(1)
|
| 199 |
if not re.search(name_re, body, re.I):
|
| 200 |
continue
|
| 201 |
+
fd = _full_date(lang, body)
|
| 202 |
+
if fd:
|
| 203 |
+
return fd, 11, body[:160], _end_year(body, int(fd[:4]))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
yr = re.search(YEAR, body)
|
| 205 |
if yr:
|
| 206 |
+
return yr.group(0) + "-00-00", 9, body[:160], _end_year(body, int(yr.group(0)))
|
| 207 |
# 2. the spouse parameter's value line: |spouse = [[Name]] (m. 1903; div 1919) / ⚭ 1903 / с 1903
|
| 208 |
for par in SPOUSE_PARAMS.get(lang, []) + SPOUSE_PARAMS.get("en", []):
|
| 209 |
for m in re.finditer(r"\|\s*" + re.escape(par) + r"\s*=([^\n]*?" + name_re + r"[^\n]*)", text, re.I):
|
| 210 |
line = m.group(1)
|
| 211 |
+
# a line can list several spouses -- "A (1890-1900); B (1902-1911)" -- the year must come from OUR
|
| 212 |
+
# spouse's SEGMENT, never the first year in the line
|
| 213 |
+
for seg in re.split(r"(?<=\))\s*;|<br\s*/?>|\}\}\s*\{\{", line): # a spouse separator is ");" — semicolons INSIDE a parenthetical stay
|
| 214 |
+
if not re.search(name_re, seg, re.I):
|
| 215 |
+
continue
|
| 216 |
+
fd = _full_date(lang, seg)
|
| 217 |
+
if fd:
|
| 218 |
+
return fd, 11, seg[:160], _end_year(seg, int(fd[:4]))
|
| 219 |
+
yr = re.search(YEAR, seg)
|
| 220 |
+
if yr:
|
| 221 |
+
return yr.group(0) + "-00-00", 9, seg[:160], _end_year(seg, int(yr.group(0)))
|
| 222 |
# 3. a parenthetical right after the spouse's name, REQUIRING a marriage marker — "(1848–1919)" is a lifespan
|
| 223 |
+
MARK = {"en": r"m\.|marr", "de": r"⚭|∞|verh", "fr": r"mari|ép", "es": r"matr|casad", "it": r"spos", "ru": r"брак|с\s", "uk": r"шлюб|з\s", "pl": r"ślub|od\s", "pt": r"casad", "nl": r"getr", "sv": r"gift", "da": r"gift", "fi": r"avio", "cs": r"sňat|od\s", "hu": r"házas", "tr": r"evl", "fa": r"ازدواج", "ar": r"تزوج|زواج", "ja": r"結婚", "zh": r"结婚|結婚", "hy": r"ամուսն"}
|
| 224 |
+
mark = MARK.get(lang, r"⚭|∞|m\.")
|
| 225 |
m = re.search(name_re + r"[^\n(]{0,40}\((?:[^)]{0,40}?(?:" + mark + r")[^)]{0,40}?)" + YEAR + r"[^)]*\)", text, re.I)
|
| 226 |
if m:
|
| 227 |
yr = re.search(YEAR, m.group(0))
|
| 228 |
+
return yr.group(0) + "-00-00", 9, m.group(0)[:160], _end_year(m.group(0), int(yr.group(0)))
|
| 229 |
+
# 3b. the genealogy signs need no parenthetical: "[[Alma]] ⚭ 1902" / "∞ 1902 [[Alma]]"
|
| 230 |
+
for pat in (name_re + r"[^\n]{0,15}[⚭∞][^\n]{0,15}" + YEAR, r"[⚭∞][^\n]{0,15}" + YEAR + r"[^\n]{0,25}" + name_re, YEAR + r"[^\n]{0,15}[⚭∞][^\n]{0,25}" + name_re):
|
| 231 |
+
m = re.search(pat, text, re.I)
|
| 232 |
+
if m:
|
| 233 |
+
yr = re.search(YEAR, m.group(0))
|
| 234 |
+
return yr.group(0) + "-00-00", 9, m.group(0)[:160], _end_year(m.group(0), int(yr.group(0)))
|
| 235 |
+
# 4. PROSE (the wikis that keep marriage in running text — dewiki rejects person infoboxes altogether):
|
| 236 |
+
# "heiratete [am 9. März] 1902 [[Alma]]" / "épousa [[Alma]] en 1902" / "женился на [[Альме]] в 1902" —
|
| 237 |
+
# a marriage VERB, the spouse and a year within one clause, either order; the plausibility gate downstream
|
| 238 |
+
# still rejects any year that cannot be a wedding
|
| 239 |
+
VERB = {"de": r"heiratete|ehelichte|verm[äa]hlte|schloss.{0,20}Ehe", "fr": r"épous[ae]|se mari[ae]|mariage avec", "es": r"se cas[óo]|contrajo matrimonio",
|
| 240 |
+
"it": r"spos[òo]", "pt": r"casou(?:-se)?", "ru": r"женился|вышла замуж|вступил[аи]? в брак|обвенчал", "uk": r"одружився|вийшла заміж",
|
| 241 |
+
"pl": r"poślubił|ożenił|wyszła za", "nl": r"trouwde|huwde", "sv": r"gifte sig", "da": r"giftede sig", "fi": r"avioitui|meni naimisiin",
|
| 242 |
+
"cs": r"oženil se|vdala se|vzal si", "hu": r"feleségül vette|házasságot kötött", "tr": r"evlendi", "ja": r"結婚", "zh": r"结婚|結婚",
|
| 243 |
+
"fa": r"ازدواج کرد", "ar": r"تزوج", "hy": r"ամուսնացավ|ամուսնացել", "en": r"married"}
|
| 244 |
+
verb = VERB.get(lang)
|
| 245 |
+
if verb:
|
| 246 |
+
win = r"[^\n;]{0,90}" # periods allowed: German ordinal dates ("9. März 1902") sit inside the clause
|
| 247 |
+
V = r"(?:" + verb + r")"
|
| 248 |
+
for pat in (V + win + name_re + win + YEAR, V + win + YEAR + win + name_re, name_re + win + V + win + YEAR,
|
| 249 |
+
YEAR + win + V + win + name_re, name_re + win + YEAR + win + V, YEAR + win + name_re + win + V): # all six orders
|
| 250 |
+
m = re.search(pat, text, re.I)
|
| 251 |
+
if m:
|
| 252 |
+
clause = text[max(0, m.start() - 24):m.end()] # a day often precedes the matched year ("9 марта 1902 ...")
|
| 253 |
+
fd = _full_date(lang, clause)
|
| 254 |
+
if fd:
|
| 255 |
+
return fd, 11, clause[:160], _end_year(clause, int(fd[:4]))
|
| 256 |
+
yr = re.search(YEAR, m.group(0))
|
| 257 |
+
return yr.group(0) + "-00-00", 9, m.group(0)[:160], _end_year(m.group(0), int(yr.group(0)))
|
| 258 |
return None
|
| 259 |
|
| 260 |
|
|
|
|
| 295 |
continue
|
| 296 |
hit = _find_dates(lang, text, names)
|
| 297 |
if hit:
|
| 298 |
+
st, prec, snip, endy = hit; sy = int(st[:4])
|
| 299 |
by = [int(r[k][:4]) for k in ("adob", "bdob") if r[k][:4].isdigit()]
|
| 300 |
ey = [int(r[k][:4]) for k in ("end", "adeath", "bdeath") if r[k][:4].isdigit()]
|
| 301 |
# a wedding before either 14th birthday, or after the marriage's own end, is a misread — dropped
|
|
|
|
| 303 |
continue
|
| 304 |
if ey and sy > min(ey):
|
| 305 |
continue
|
| 306 |
+
w.writerow([r["a"], r["b"], st, prec, lang, subj, snip.replace("\n", " "), endy if endy is not None else ""]); got += 1
|
| 307 |
if i % 400 == 0 and i:
|
| 308 |
f.flush(); log(f" {lang}: {i:,}/{len(jobs):,} read · {got:,} dates found")
|
| 309 |
time.sleep(0.6)
|