added initial version of revision-scraper
[covid19.git] / wikipedia_views / 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_views/data", type=str)
28     parser.add_argument('-i', '--article_file', help='File listing article names', default="wikipedia_views/resources/enwp_wikiproject_covid19_articles.txt", type=str)
29     parser.add_argument('-d', '--query_date', help='Date if not yesterday, in YYYYMMDD format.', type=str)
30     parser.add_argument('-L', '--logging_level', help='Logging level. Options are debug, info, warning, error, critical. Default: info.', default='info', type=str), 
31     parser.add_argument('-W', '--logging_destination', help='Logging destination file. (default: standard error)', type=str), 
32     args = parser.parse_args()
33     return(args)
34
35 def main():
36     args = parse_args()
37
38     output_path = args.output_folder
39     article_filename = args.article_file
40     #handle -d
41     if args.query_date:
42         query_date = args.query_date
43     else:
44         yesterday = datetime.datetime.today() - datetime.timedelta(days=1)
45         query_date = yesterday.strftime("%Y%m%d")
46
47     query_data  = query_date + "00" #requires specifying hours
48
49     #handle -L
50     loglevel_mapping = { 'debug' : logging.DEBUG,
51                          'info' : logging.INFO,
52                          'warning' : logging.WARNING,
53                          'error' : logging.ERROR,
54                          'critical' : logging.CRITICAL }
55
56     if args.logging_level in loglevel_mapping:
57         loglevel = loglevel_mapping[args.logging_level]
58     else:
59         print("Choose a valid log level: debug, info, warning, error, or critical") 
60         exit
61
62     #handle -W
63     if args.logging_destination:
64         logging.basicConfig(filename=args.logging_destination, filemode='a', level=loglevel)
65     else:
66         logging.basicConfig(level=loglevel)
67
68     export_git_hash = subprocess.check_output(['git', 'rev-parse', 'HEAD']).decode().strip()
69     export_git_short_hash = subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD']).decode().strip()
70     export_time = str(datetime.datetime.now())
71
72     logging.info(f"Starting run at {export_time}")
73     logging.info(f"Last commit: {export_git_hash}")
74
75     json_output_filename = os.path.join(output_path, f"digobs_covid19-wikipedia-enwiki_revisions-{query_date}.json")
76     tsv_output_filename =  os.path.join(output_path, f"digobs_covid19-wikipedia-enwiki_revisions-{query_data}.tsv")
77     
78     api_session = api.Session("https://en.wikipedia.org/w/api.php")
79
80     # list of properties from the API we want to gather (basically all of
81     # them supported by mediawik-utilities)
82
83     rv_props =  {'revid' : 'ids',
84                  'timestamp' : 'timestamp',
85                  'user' : 'user',
86                  'userid' : 'userid',
87                  'size' : 'size',
88                  'sha1' : 'sha1',
89                  'contentmodel' : 'contentmodel',
90                  'tags' : 'tags',
91                  'comment' : 'comment',
92                  'content' : 'content' }
93
94     exclude_from_tsv = ['tags', 'comment', 'content']
95
96     # load the list of articles
97     with open(article_filename, 'r') as infile:
98         article_list = [art.strip() for art in list(infile)]
99
100     def get_revisions_for_page(title):
101         return api_session.revisions.query(properties=rv_props.values(),
102                                            titles={title},
103                                            direction="newer")
104
105     tsv_fields = ['title', 'pageid', 'namespace']
106     tsv_fields = tsv_fields + list(rv_props.keys())
107
108     # drop fields that we identified for exclusion
109     tsv_fields = [e for e in tsv_fields if e not in exclude_from_tsv]
110
111     # add special export fields
112     tsv_fields = tsv_fields + ['url', 'export_timestamp', 'export_commit']
113
114     export_info = { 'git_commit' : export_git_hash,
115                     'timestamp' : export_time }
116
117     with open(json_output_filename, 'w') as json_output, \
118          open(tsv_output_filename, 'w') as tsv_output:
119
120         tsv_writer = DictWriter(tsv_output, fieldnames=tsv_fields, delimiter="\t")
121         tsv_writer.writeheader()
122
123         for article in article_list:
124             logging.info(f"pulling revisiosn for: {article}")
125             for rev in get_revisions_for_page(article):
126                 logging.debug(f"processing raw revision: {rev}")
127
128                 # add export metadata
129                 rev['exported'] = export_info
130
131                 # save the json version of the code
132                 print(json.dumps(rev), file=json_output)
133
134                 # handle missing data
135                 if "sha1" not in rev:
136                     rev["sha1"] = ""
137
138                 # add page title information
139                 rev['title'] = rev['page']['title']
140                 rev['pageid'] = rev['page']['pageid']
141                 rev['namespace'] = rev['page']['ns']
142
143                 # construct a URL
144                 rev['url'] = Request('GET', 'https://en.wikipedia.org/w/index.php',
145                                      params={'title' : rev['title'].replace(" ", "_"),
146                                             'oldid' : rev['revid']}).prepare().url
147
148                 rev['export_timestamp'] = export_time
149                 rev['export_commit'] = export_git_short_hash
150
151                 tsv_writer.writerow({k: rev[k] for k in tsv_fields})
152             break
153
154 if __name__ == "__main__":
155
156     main()

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