]> code.communitydata.science - covid19.git/blob - wikipedia/scripts/fetch_enwiki_revisions.py
tweaks to revision export code
[covid19.git] / wikipedia / scripts / fetch_enwiki_revisions.py
1 #!/usr/bin/env python3
2
3 ###############################################################################
4 #
5 # This script assumes the presence of the COVID-19 repo.
6
7 # It (1) reads in the article list and then (2) calls the Wikimedia API to 
8 # fetch view information for each article. Output is to (3) JSON and TSV.
9 #
10 ###############################################################################
11
12 import argparse
13 import logging
14 import os.path
15 import json
16 import subprocess
17 import datetime
18
19 from requests import Request
20 from csv import DictWriter
21 from mw import api
22
23
24 def parse_args():
25
26     parser = argparse.ArgumentParser(description='Call the views API to collect Wikipedia revision data.')
27     parser.add_argument('-o', '--output_folder', help='Where to save output', default="wikipedia/data", type=str)
28     parser.add_argument('-i', '--article_file', help='File listing article names', default="wikipedia/resources/enwp_wikiproject_covid19_articles.txt", type=str)
29     parser.add_argument('-L', '--logging_level', help='Logging level. Options are debug, info, warning, error, critical. Default: info.', default='info', type=str), 
30     parser.add_argument('-W', '--logging_destination', help='Logging destination file. (default: standard error)', type=str), 
31     args = parser.parse_args()
32     return(args)
33
34 def main():
35     args = parse_args()
36
37     output_path = args.output_folder
38     article_filename = args.article_file
39
40     #handle -L
41     loglevel_mapping = { 'debug' : logging.DEBUG,
42                          'info' : logging.INFO,
43                          'warning' : logging.WARNING,
44                          'error' : logging.ERROR,
45                          'critical' : logging.CRITICAL }
46
47     if args.logging_level in loglevel_mapping:
48         loglevel = loglevel_mapping[args.logging_level]
49     else:
50         print("Choose a valid log level: debug, info, warning, error, or critical") 
51         exit
52
53     #handle -W
54     if args.logging_destination:
55         logging.basicConfig(filename=args.logging_destination, filemode='a', level=loglevel)
56     else:
57         logging.basicConfig(level=loglevel)
58
59     export_git_hash = subprocess.check_output(['git', 'rev-parse', 'HEAD']).decode().strip()
60     export_git_short_hash = subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD']).decode().strip()
61     export_time = str(datetime.datetime.now())
62     export_date = datetime.datetime.today().strftime("%Y%m%d")
63
64     logging.info(f"Starting run at {export_time}")
65     logging.info(f"Last commit: {export_git_hash}")
66
67     json_output_filename = os.path.join(output_path, f"digobs_covid19-wikipedia-enwiki_revisions-{export_date}.json")
68     tsv_output_filename =  os.path.join(output_path, f"digobs_covid19-wikipedia-enwiki_revisions-{export_date}.tsv")
69     
70     api_session = api.Session("https://en.wikipedia.org/w/api.php")
71
72     # list of properties from the API we want to gather (basically all of
73     # them supported by mediawik-utilities)
74
75     rv_props =  {'revid' : 'ids',
76                  'timestamp' : 'timestamp',
77                  'user' : 'user',
78                  'userid' : 'userid',
79                  'size' : 'size',
80                  'sha1' : 'sha1',
81                  'contentmodel' : 'contentmodel',
82                  'tags' : 'tags',
83                  'flags' : 'flags',
84                  'comment' : 'comment',
85                  'content' : 'content' }
86
87     exclude_from_tsv = ['tags', 'comment', 'content', 'flags']
88
89     # load the list of articles
90     with open(article_filename, 'r') as infile:
91         article_list = [art.strip() for art in list(infile)]
92
93     def get_revisions_for_page(title):
94         return api_session.revisions.query(properties=rv_props.values(),
95                                            titles={title},
96                                            direction="newer")
97
98     tsv_fields = ['title', 'pageid', 'namespace']
99     tsv_fields = tsv_fields + list(rv_props.keys())
100
101     # drop fields that we identified for exclusion
102     tsv_fields = [e for e in tsv_fields if e not in exclude_from_tsv]
103
104     # add special export fields
105     tsv_fields = tsv_fields + ['anon', 'minor', 'url', 'export_timestamp', 'export_commit']
106
107     export_info = { 'git_commit' : export_git_hash,
108                     'timestamp' : export_time }
109
110     with open(json_output_filename, 'w') as json_output, \
111          open(tsv_output_filename, 'w') as tsv_output:
112
113         tsv_writer = DictWriter(tsv_output, fieldnames=tsv_fields, delimiter="\t")
114         tsv_writer.writeheader()
115
116         for article in article_list:
117             logging.info(f"pulling revisions for: {article}")
118             for rev in get_revisions_for_page(article):
119                 logging.debug(f"processing raw revision: {rev}")
120
121                 # add export metadata
122                 rev['exported'] = export_info
123
124                 # save the json version of the code
125                 print(json.dumps(rev), file=json_output)
126
127                 # handle missing data
128                 if "sha1" not in rev:
129                     rev["sha1"] = ""
130
131                 if "userhidden" in rev:
132                     rev["user"] = ""
133                     rev["userid"] = ""
134
135                 # recode anon so it's true or false instead of present/missing
136                 if "anon" in rev:
137                     rev["anon"] = True
138                 else:
139                     rev["anon"] = False
140                     
141                 # let's recode "minor" in the same way
142                 if "minor" in rev:
143                     rev["minor"] = True
144                 else:
145                     rev["minor"] = False
146
147                 # add page title information
148                 rev['title'] = rev['page']['title']
149                 rev['pageid'] = rev['page']['pageid']
150                 rev['namespace'] = rev['page']['ns']
151
152                 # construct a URL
153                 rev['url'] = Request('GET', 'https://en.wikipedia.org/w/index.php',
154                                      params={'title' : rev['title'].replace(" ", "_"),
155                                             'oldid' : rev['revid']}).prepare().url
156
157                 rev['export_timestamp'] = export_time
158                 rev['export_commit'] = export_git_short_hash
159
160                 tsv_writer.writerow({k: rev[k] for k in tsv_fields})
161
162 if __name__ == "__main__":
163     main()

Community Data Science Collective || Want to submit a patch?