Spaces:
Running
Running
Commit ·
5441c2f
1
Parent(s): 48aff15
feat: extract dates from HTML-scraped articles
Browse files- src/html_scraper.py +66 -2
src/html_scraper.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
| 1 |
import logging
|
|
|
|
| 2 |
import time
|
|
|
|
| 3 |
from urllib.parse import urljoin, urlparse
|
| 4 |
|
| 5 |
import requests
|
|
@@ -60,6 +62,7 @@ class HTMLSiteScraper:
|
|
| 60 |
continue
|
| 61 |
seen_urls.add(full_url)
|
| 62 |
domain = site.get("domain") or urlparse(full_url).netloc
|
|
|
|
| 63 |
post = RedditPost(
|
| 64 |
id=full_url,
|
| 65 |
title=title,
|
|
@@ -69,8 +72,8 @@ class HTMLSiteScraper:
|
|
| 69 |
num_comments=0,
|
| 70 |
source_domain=domain,
|
| 71 |
image_url="",
|
| 72 |
-
published=
|
| 73 |
-
published_iso=
|
| 74 |
)
|
| 75 |
posts.append(post)
|
| 76 |
except Exception as exc:
|
|
@@ -78,6 +81,28 @@ class HTMLSiteScraper:
|
|
| 78 |
time.sleep(1.0)
|
| 79 |
return posts
|
| 80 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
@staticmethod
|
| 82 |
def _find_article_links(soup: BeautifulSoup, site: dict) -> list:
|
| 83 |
for selector in site["selectors"]:
|
|
@@ -85,3 +110,42 @@ class HTMLSiteScraper:
|
|
| 85 |
if found:
|
| 86 |
return found
|
| 87 |
return []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import logging
|
| 2 |
+
import re
|
| 3 |
import time
|
| 4 |
+
from datetime import datetime
|
| 5 |
from urllib.parse import urljoin, urlparse
|
| 6 |
|
| 7 |
import requests
|
|
|
|
| 62 |
continue
|
| 63 |
seen_urls.add(full_url)
|
| 64 |
domain = site.get("domain") or urlparse(full_url).netloc
|
| 65 |
+
published, published_iso = self._extract_date(soup, a_tag)
|
| 66 |
post = RedditPost(
|
| 67 |
id=full_url,
|
| 68 |
title=title,
|
|
|
|
| 72 |
num_comments=0,
|
| 73 |
source_domain=domain,
|
| 74 |
image_url="",
|
| 75 |
+
published=published,
|
| 76 |
+
published_iso=published_iso,
|
| 77 |
)
|
| 78 |
posts.append(post)
|
| 79 |
except Exception as exc:
|
|
|
|
| 81 |
time.sleep(1.0)
|
| 82 |
return posts
|
| 83 |
|
| 84 |
+
@staticmethod
|
| 85 |
+
def _extract_date(soup: BeautifulSoup, a_tag) -> tuple[str, str]:
|
| 86 |
+
for parent_tag in ("div", "article", "li", "section"):
|
| 87 |
+
parent = a_tag.find_parent(parent_tag)
|
| 88 |
+
if not parent:
|
| 89 |
+
continue
|
| 90 |
+
time_tag = parent.find("time")
|
| 91 |
+
if time_tag:
|
| 92 |
+
raw = time_tag.get("datetime") or time_tag.get_text(strip=True)
|
| 93 |
+
if raw:
|
| 94 |
+
parsed = _parse_date_str(raw)
|
| 95 |
+
if parsed:
|
| 96 |
+
return parsed
|
| 97 |
+
date_el = parent.find(["span", "div"], class_=re.compile(r"date|time", re.I))
|
| 98 |
+
if date_el:
|
| 99 |
+
raw = date_el.get("datetime") or date_el.get_text(strip=True)
|
| 100 |
+
if raw:
|
| 101 |
+
parsed = _parse_date_str(raw)
|
| 102 |
+
if parsed:
|
| 103 |
+
return parsed
|
| 104 |
+
return ("", "")
|
| 105 |
+
|
| 106 |
@staticmethod
|
| 107 |
def _find_article_links(soup: BeautifulSoup, site: dict) -> list:
|
| 108 |
for selector in site["selectors"]:
|
|
|
|
| 110 |
if found:
|
| 111 |
return found
|
| 112 |
return []
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
_DATE_FORMATS = [
|
| 116 |
+
"%Y-%m-%dT%H:%M:%S%z",
|
| 117 |
+
"%Y-%m-%dT%H:%M:%S",
|
| 118 |
+
"%Y-%m-%d",
|
| 119 |
+
"%d %b %Y",
|
| 120 |
+
"%d %B %Y",
|
| 121 |
+
"%b %d, %Y",
|
| 122 |
+
"%B %d, %Y",
|
| 123 |
+
"%d/%m/%Y",
|
| 124 |
+
"%m/%d/%Y",
|
| 125 |
+
]
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
def _parse_date_str(raw: str) -> tuple[str, str] | None:
|
| 129 |
+
raw = raw.strip()
|
| 130 |
+
if not raw:
|
| 131 |
+
return None
|
| 132 |
+
for fmt in _DATE_FORMATS:
|
| 133 |
+
try:
|
| 134 |
+
dt = datetime.strptime(raw, fmt)
|
| 135 |
+
if dt.tzinfo:
|
| 136 |
+
display = dt.strftime("%d %b %Y")
|
| 137 |
+
iso = dt.strftime("%Y-%m-%d")
|
| 138 |
+
else:
|
| 139 |
+
display = dt.strftime("%d %b %Y")
|
| 140 |
+
iso = dt.strftime("%Y-%m-%d")
|
| 141 |
+
return (display, iso)
|
| 142 |
+
except ValueError:
|
| 143 |
+
continue
|
| 144 |
+
try:
|
| 145 |
+
dt = datetime.fromisoformat(raw)
|
| 146 |
+
display = dt.strftime("%d %b %Y")
|
| 147 |
+
iso = dt.strftime("%Y-%m-%d")
|
| 148 |
+
return (display, iso)
|
| 149 |
+
except (ValueError, TypeError):
|
| 150 |
+
pass
|
| 151 |
+
return None
|