-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathjson-processor.py
More file actions
216 lines (190 loc) · 7.08 KB
/
Copy pathjson-processor.py
File metadata and controls
216 lines (190 loc) · 7.08 KB
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
import json
import os
import html
from datetime import date, datetime, timezone
import re
from sqlite3 import connect, Error
INPUT_DIR = "json/"
INVALID_LOG_PATH = "invalid-dates.log"
DB_PATH = "anon.db"
bold_tag = re.compile(r"<b>", re.MULTILINE)
pub_date_re = re.compile(r".*(\d\d\d\d)/(\d\d)/(\d\d).*", re.MULTILINE)
# Substring of displayLink -> canonical source stored in the DB.
# Checked in order; last match wins (same as the original stacked ifs).
SOURCE_OVERRIDES = {
'nytimes.com': 'www.nytimes.com',
'washingtonpost.com': 'www.washingtonpost.com',
'abcnews.go.com': 'abcnews.go.com',
'nbcnews.com': 'www.nbcnews.com',
'foxnews.com': 'www.foxnews.com',
'apnews.com': 'www.apnews.com',
'bloomberg.com': 'www.bloomberg.com',
'usatoday.com': 'www.usatoday.com',
'wsj.com': 'www.wsj.com',
'politico.com': 'www.politico.com',
'cnn.com': 'www.cnn.com',
'cbsnews.com': 'www.cbsnews.com',
'cnbc.com': 'www.cnbc.com',
'mcclatchydc.com': 'www.mcclatchydc.com',
'reuters.com': 'www.reuters.com',
'msnbc.com': 'www.msnbc.com',
'theguardian.com': 'www.theguardian.com',
'time.com': 'time.com',
'newsweek.com': 'www.newsweek.com',
'msn.com': 'www.msn.com',
'bbc.com': 'www.bbc.com',
'nypost.com': 'nypost.com',
'latimes.com': 'www.latimes.com',
'axios.com': 'www.axios.com',
'yahoo.com': 'www.yahoo.com',
'startribune.com': 'www.startribune.com',
'ft.com': 'www.ft.com',
'sfchronicle.com': 'www.sfchronicle.com',
'propublica.org': 'www.propublica.org',
'chicagotribune.com': 'www.chicagotribune.com',
'vox.com': 'www.vox.com',
'washingtonexaminer.com': 'www.washingtonexaminer.com',
'washingtontimes.com': 'www.washingtontimes.com',
'theglobeandmail.com': 'www.theglobeandmail.com',
'theaustralian.com.au': 'www.theaustralian.com.au',
'thehill.com': 'thehill.com',
'npr.org': 'www.npr.org',
'newsday.com': 'www.newsday.com',
'ms.now': 'www.ms.now'
}
# Paths into item['pagemap'] where a publish date might live.
# Checked in order; LAST successful lookup wins (matches original stacked try/except/pass).
PUB_DATE_PATHS = [
('newsarticle', 0, 'datepublished'),
('metatags', 0, 'article:published'),
('article', 0, 'datepublished'),
('metatags', 0, 'date'),
('metatags', 0, 'iso-8601-publish-date'),
('metatags', 0, 'analyticsattributes.articledate'),
('metatags', 0, 'sailthru.date'),
('metatags', 0, 'article:published_time'),
('metatags', 0, 'dc.date'),
]
def normalize_source(item_source):
for needle, replacement in SOURCE_OVERRIDES.items():
if needle in item_source:
item_source = replacement
return item_source
def extract_publish_date(item):
pagemap = item.get('pagemap', {})
item_published = None
for path in PUB_DATE_PATHS:
try:
value = pagemap
for key in path:
value = value[key]
item_published = value
except (KeyError, IndexError, TypeError):
continue
return item_published
def is_valid_date(value):
if isinstance(value, date):
return True
if not value:
return False
if isinstance(value, (int, float)):
try:
datetime.fromtimestamp(float(value), tz=timezone.utc)
return True
except (OverflowError, OSError, ValueError):
return False
if isinstance(value, str):
stripped = value.strip()
if not stripped:
return False
if re.fullmatch(r"-?\d+(?:\.\d+)?", stripped):
try:
datetime.fromtimestamp(float(stripped), tz=timezone.utc)
return True
except (OverflowError, OSError, ValueError):
return False
try:
datetime.strptime(stripped, "%Y-%m-%d")
return True
except ValueError:
return False
return False
def log_invalid_date(item_link, today_value, publish_date_value, log_path=INVALID_LOG_PATH):
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
entry = (
f"{timestamp}\turl={item_link}\t"
f"today={today_value}\tpublish_date={publish_date_value}\n"
)
with open(log_path, "a", encoding="utf-8") as log_file:
log_file.write(entry)
def update_database(conn, today, fields):
item_source, item_phrase, item_title, item_link, item_snippet, publish_date = fields
if not (is_valid_date(today) and is_valid_date(publish_date)):
log_invalid_date(item_link, today, publish_date)
print("Skipping. Invalid date for today or publish_date.")
return
if not bold_tag.search(item_snippet):
print("Skipping. No anonymous phrase in the entry.")
return
today_str = today.isoformat() if isinstance(today, date) else today
try:
conn.execute(
"INSERT INTO anon VALUES (?, ?, ?, ?, ?, ?, ?)",
[item_source, item_phrase, item_title, item_link, item_snippet, today_str, publish_date],
)
conn.commit()
print("New entry inserted in the database.")
except Error as e:
print("Oops: ", e.args[0])
def process_search_results(conn, today, results_json):
try:
item_count = results_json["queries"]["request"][0]["count"]
item_phrase = results_json["queries"]["request"][0]["searchTerms"]
except (KeyError, IndexError):
print("There were no matches in this query.")
return
for i in range(item_count):
try:
item = results_json["items"][i]
item_source = normalize_source(item["displayLink"])
item_title = item["title"]
item_link = item["link"]
item_snippet = html.unescape(item["htmlSnippet"])
except (KeyError, IndexError):
continue
item_published = extract_publish_date(item)
if 'washingtonpost' in item_link or 'usatoday' in item_link:
match = pub_date_re.search(item_link)
if match:
item_published = f"{match[1]}-{match[2]}-{match[3]}"
if item_published is None:
continue
publish_date_parsed = re.sub(r'(\d\d\d\d-\d\d-\d\d).*', r'\1', item_published)
update_database(
conn,
today,
[item_source, item_phrase, item_title, item_link, item_snippet, publish_date_parsed],
)
def load_json_file(filepath):
try:
with open(filepath, encoding="utf-8") as f:
return json.load(f)
except UnicodeDecodeError:
with open(filepath, encoding="latin-1") as f:
return json.load(f)
def main(input_dir=INPUT_DIR, db_path=DB_PATH):
conn = connect(db_path)
today = date.today()
try:
for filename in os.listdir(input_dir):
filepath = os.path.join(input_dir, filename)
try:
data = load_json_file(filepath)
except Exception as e:
print(f"Error processing file {filename}: {e}")
continue
process_search_results(conn, today, data)
finally:
conn.close()
if __name__ == "__main__":
main()