citation-map 3.7__tar.gz → 3.9__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: citation-map
3
- Version: 3.7
3
+ Version: 3.9
4
4
  Summary: A simple tool to generate your Google Scholar citation world map.
5
5
  Home-page: https://github.com/ChenLiu-1996/CitationMap
6
6
  Author: Chen Liu
@@ -43,10 +43,6 @@ If you open it on a browser, you will see your own version of the following cita
43
43
 
44
44
  ## Usage
45
45
  1. Install the package.
46
- ```
47
- pip install citation-map
48
- ```
49
- Remember to upgrade to the latest version if you installed an earlier version.
50
46
  ```
51
47
  pip install citation-map --upgrade
52
48
  ```
@@ -91,8 +87,23 @@ If you open it on a browser, you will see your own version of the following cita
91
87
  If true, print the list of citing institutions (affiliations of citing authors).
92
88
  ```
93
89
 
90
+ ## Limitations
91
+
92
+ 1. This tool is purely based on Google Scholar. As a result, you are expected to have underestimations due to reasons such as:
93
+ - Your Google Scholar profile is not up-to-date.
94
+ - Some papers citing you are not indexed by Google Scholar.
95
+ - Some authors citing you do not have Google Scholar profile.
96
+ - Some authors citing you do not report their affiliations.
97
+ 2. `geopy.geocoders` is used to convert the citing authors' affiliations to geographic coordinates. To facilitate the process, I used some simple rule-based natural language processing to clean up the affiliations. As a result, you are expected to have:
98
+ - Underestimation if correct affiliations are not found by `geopy.geocoders`.
99
+ - Underestimation if we experience communication error with `geopy.geocoders`.
100
+ - Overestimation if non-affiliation terms are incorrectly identified as locations by `geopy.geocoders`.
101
+
102
+ **Please raise an issue or submit a pull request if you have some good idea to better process the affiliation string. Note that currently I am not considering any paid service or tools that pose extra burden on the users, such as GPT API.**
103
+
104
+
94
105
  ## Debug
95
- 1. `scholarly._proxy_generator.MaxTriesExceededException` or `[WARNING!] Blocked by CAPTCHA or robot check`
106
+ 1. `MaxTriesExceededException` or `[WARNING!] Blocked by CAPTCHA or robot check`
96
107
 
97
108
  - From my experience, both are good indicators that your IP address is blocked by Google Scholar due to excessive crawling (using the `scholarly` package).
98
109
  - One hot fix I found was to hop on a University VPN and run again. I typically experience this error after running the tool twice, and I need to disconnect and reconnect my VPN to "unblock" myself.
@@ -100,33 +111,49 @@ If you open it on a browser, you will see your own version of the following cita
100
111
 
101
112
  ## Changelog
102
113
 
103
- ### Version 3.7
104
- I updated the logic for webscraping and avoided using `scholarly.citeby()` which is the biggest trigger of blacklisting from Google Scholar.
114
+ <details>
115
+ <summary>Version 3.9</summary>
116
+ <br>
117
+ In 3.9, I slightly improved the logic for affiliation extraction.
118
+
119
+ In 3.8, I removed multiprocessing for `geopy.geocoders` as per their official documentation. Also I cleaned up some unnecessary `scholarly` calls which further helps us not get blacklisted by Google Scholar.
120
+
121
+ In 3.7, I updated the logic for webscraping and avoided using `scholarly.citeby()` which is the biggest trigger of blacklisting from Google Scholar.
105
122
 
106
- **Now we should be able to handle users with more citations than before. I tested on a profile with 400 citations without running into any issue.**
123
+ **Now we should be able to handle users with more citations than before. I tested on a profile with 1000 citations without running into any issue.**
124
+ </details>
107
125
 
108
- ### Version 3.0
109
- I realized a problem with how I used `geopy.geocoders`. A majority of the authors' self-entered affiliations include details that are irrelevant to the affiliation itself. Therefore, they are not successfully found in the system and hence are not converted to geographic coordinates on the world map.
126
+ <details>
127
+ <summary>Version 3.0</summary>
128
+ <br>
129
+ I realized a problem with how I used `geopy.geocoders`. A majority of the authors' affiliations include details that are irrelevant to the affiliation itself. Therefore, they are not successfully found in the system and hence are not converted to geographic coordinates on the world map.
110
130
 
111
131
  For example, we would want the substring "Yale University" from the string "Assistant Professor at Yale University".
112
132
 
113
133
  I applied a simple fix with some rule-based natural language processing. This helps us identify many missing citing locations.
114
134
 
115
135
  **Please raise an issue or submit a pull request if you have some good idea to better process the affiliation string. Note that currently I am not considering any paid service or tools that pose extra burden on the users, such as GPT API.**
136
+ </details>
116
137
 
117
- ### Version 2.0
138
+ <details>
139
+ <summary>Version 2.0</summary>
140
+ <br>
118
141
  I finally managed to **drastically speed up** the process using multiprocessing, in a way that avoids being blocked by Google Scholar.
119
142
 
120
143
  On my personal computer, processing my profile with 100 citations took 1 hour with version 1.0 while it's now taking 5 minutes with version 2.0.
121
144
 
122
145
  With that said, please be careful and do not run this tool frequently. I can easily get on Google Scholar's blacklist after a few runs.
146
+ </details>
123
147
 
124
- ### Version 1.0
148
+ <details>
149
+ <summary>Version 1.0</summary>
150
+ <br>
125
151
  Very basic functionality.
126
152
 
127
153
  This script is a bit slow. On my personal computer, it takes half a minute to process each citation. If you have thousands of citations, it may or may not be a good idea to use this script.
128
154
 
129
155
  I tried to use multiprocessing, but unfortunately the excessive visits get me blocked by Google Scholar.
156
+ </details>
130
157
 
131
158
  ## Dependencies
132
159
  Dependencies (`scholarly`, `geopy`, `folium`, `tqdm`) are already taken care of when you install via pip.
@@ -27,10 +27,6 @@ If you open it on a browser, you will see your own version of the following cita
27
27
 
28
28
  ## Usage
29
29
  1. Install the package.
30
- ```
31
- pip install citation-map
32
- ```
33
- Remember to upgrade to the latest version if you installed an earlier version.
34
30
  ```
35
31
  pip install citation-map --upgrade
36
32
  ```
@@ -75,8 +71,23 @@ If you open it on a browser, you will see your own version of the following cita
75
71
  If true, print the list of citing institutions (affiliations of citing authors).
76
72
  ```
77
73
 
74
+ ## Limitations
75
+
76
+ 1. This tool is purely based on Google Scholar. As a result, you are expected to have underestimations due to reasons such as:
77
+ - Your Google Scholar profile is not up-to-date.
78
+ - Some papers citing you are not indexed by Google Scholar.
79
+ - Some authors citing you do not have Google Scholar profile.
80
+ - Some authors citing you do not report their affiliations.
81
+ 2. `geopy.geocoders` is used to convert the citing authors' affiliations to geographic coordinates. To facilitate the process, I used some simple rule-based natural language processing to clean up the affiliations. As a result, you are expected to have:
82
+ - Underestimation if correct affiliations are not found by `geopy.geocoders`.
83
+ - Underestimation if we experience communication error with `geopy.geocoders`.
84
+ - Overestimation if non-affiliation terms are incorrectly identified as locations by `geopy.geocoders`.
85
+
86
+ **Please raise an issue or submit a pull request if you have some good idea to better process the affiliation string. Note that currently I am not considering any paid service or tools that pose extra burden on the users, such as GPT API.**
87
+
88
+
78
89
  ## Debug
79
- 1. `scholarly._proxy_generator.MaxTriesExceededException` or `[WARNING!] Blocked by CAPTCHA or robot check`
90
+ 1. `MaxTriesExceededException` or `[WARNING!] Blocked by CAPTCHA or robot check`
80
91
 
81
92
  - From my experience, both are good indicators that your IP address is blocked by Google Scholar due to excessive crawling (using the `scholarly` package).
82
93
  - One hot fix I found was to hop on a University VPN and run again. I typically experience this error after running the tool twice, and I need to disconnect and reconnect my VPN to "unblock" myself.
@@ -84,33 +95,49 @@ If you open it on a browser, you will see your own version of the following cita
84
95
 
85
96
  ## Changelog
86
97
 
87
- ### Version 3.7
88
- I updated the logic for webscraping and avoided using `scholarly.citeby()` which is the biggest trigger of blacklisting from Google Scholar.
98
+ <details>
99
+ <summary>Version 3.9</summary>
100
+ <br>
101
+ In 3.9, I slightly improved the logic for affiliation extraction.
102
+
103
+ In 3.8, I removed multiprocessing for `geopy.geocoders` as per their official documentation. Also I cleaned up some unnecessary `scholarly` calls which further helps us not get blacklisted by Google Scholar.
104
+
105
+ In 3.7, I updated the logic for webscraping and avoided using `scholarly.citeby()` which is the biggest trigger of blacklisting from Google Scholar.
89
106
 
90
- **Now we should be able to handle users with more citations than before. I tested on a profile with 400 citations without running into any issue.**
107
+ **Now we should be able to handle users with more citations than before. I tested on a profile with 1000 citations without running into any issue.**
108
+ </details>
91
109
 
92
- ### Version 3.0
93
- I realized a problem with how I used `geopy.geocoders`. A majority of the authors' self-entered affiliations include details that are irrelevant to the affiliation itself. Therefore, they are not successfully found in the system and hence are not converted to geographic coordinates on the world map.
110
+ <details>
111
+ <summary>Version 3.0</summary>
112
+ <br>
113
+ I realized a problem with how I used `geopy.geocoders`. A majority of the authors' affiliations include details that are irrelevant to the affiliation itself. Therefore, they are not successfully found in the system and hence are not converted to geographic coordinates on the world map.
94
114
 
95
115
  For example, we would want the substring "Yale University" from the string "Assistant Professor at Yale University".
96
116
 
97
117
  I applied a simple fix with some rule-based natural language processing. This helps us identify many missing citing locations.
98
118
 
99
119
  **Please raise an issue or submit a pull request if you have some good idea to better process the affiliation string. Note that currently I am not considering any paid service or tools that pose extra burden on the users, such as GPT API.**
120
+ </details>
100
121
 
101
- ### Version 2.0
122
+ <details>
123
+ <summary>Version 2.0</summary>
124
+ <br>
102
125
  I finally managed to **drastically speed up** the process using multiprocessing, in a way that avoids being blocked by Google Scholar.
103
126
 
104
127
  On my personal computer, processing my profile with 100 citations took 1 hour with version 1.0 while it's now taking 5 minutes with version 2.0.
105
128
 
106
129
  With that said, please be careful and do not run this tool frequently. I can easily get on Google Scholar's blacklist after a few runs.
130
+ </details>
107
131
 
108
- ### Version 1.0
132
+ <details>
133
+ <summary>Version 1.0</summary>
134
+ <br>
109
135
  Very basic functionality.
110
136
 
111
137
  This script is a bit slow. On my personal computer, it takes half a minute to process each citation. If you have thousands of citations, it may or may not be a good idea to use this script.
112
138
 
113
139
  I tried to use multiprocessing, but unfortunately the excessive visits get me blocked by Google Scholar.
140
+ </details>
114
141
 
115
142
  ## Dependencies
116
143
  Dependencies (`scholarly`, `geopy`, `folium`, `tqdm`) are already taken care of when you install via pip.
@@ -1,15 +1,16 @@
1
+ import itertools
1
2
  import re
2
3
  import random
3
4
  import time
4
- import itertools
5
5
 
6
+ from multiprocessing import Pool
7
+ import pycountry
8
+ from tqdm import tqdm
6
9
  from typing import List, Tuple
7
- from scholarly import scholarly, ProxyGenerator
8
- from geopy.geocoders import Nominatim
9
10
 
10
11
  import folium
11
- from tqdm import tqdm
12
- from multiprocessing import Pool
12
+ from geopy.geocoders import Nominatim
13
+ from scholarly import scholarly, ProxyGenerator
13
14
 
14
15
  from .scholarly_support import get_author_ids
15
16
 
@@ -24,58 +25,71 @@ def fetch_google_scholar_profile(scholar_id: str):
24
25
 
25
26
  def find_all_citing_institutions(publications, num_processes: int = 16) -> List[Tuple[str]]:
26
27
  '''
27
- Step 2. Final all citing institutions (i.e., insitutions of citing authors).
28
+ Step 2. Find all citing institutions (i.e., insitutions of citing authors).
28
29
  '''
30
+ # Fetch metadata for all publications.
29
31
  if num_processes > 1 and isinstance(num_processes, int):
30
- # Fetch metadata for all publications, in parallel.
31
32
  with Pool(processes=num_processes) as pool:
32
33
  all_publications = list(tqdm(pool.imap(__fill_publication_metadata, publications),
33
- desc='Filling metadata for all publications...',
34
- total=len(publications)))
34
+ desc='Filling metadata for your %d publications' % len(publications),
35
+ total=len(publications)))
35
36
  else:
36
37
  all_publications = []
37
- for pub in tqdm(publications, desc='Filling metadata for all publications...', total=len(publications)):
38
+ for pub in tqdm(publications,
39
+ desc='Filling metadata for your %d publications' % len(publications),
40
+ total=len(publications)):
38
41
  all_publications.append(__fill_publication_metadata(pub))
39
42
 
43
+ # Convert all publications to Google Scholar publication IDs.
44
+ # This is fast and no parallel processing is needed.
40
45
  all_publication_ids = []
41
46
  for pub in all_publications:
42
47
  if 'cites_id' in pub:
43
48
  for cites_id in pub['cites_id']:
44
49
  all_publication_ids.append(cites_id)
45
50
 
51
+ # Find all citing authors from all publications.
46
52
  if num_processes > 1 and isinstance(num_processes, int):
47
- # Find all citing papers from all publications, in parallel.
48
53
  with Pool(processes=num_processes) as pool:
49
54
  all_citing_authors_nested = list(tqdm(pool.imap(__citing_author_ids_from_publication, all_publication_ids),
50
- desc='Finding all citing authors..',
55
+ desc='Finding citing authors from your %d publications' % len(all_publication_ids),
51
56
  total=len(all_publication_ids)))
52
57
  else:
53
58
  all_citing_authors_nested = []
54
- for pub in tqdm(all_publication_ids, desc='Finding all citing authors...', total=len(all_publication_ids)):
59
+ for pub in tqdm(all_publication_ids,
60
+ desc='Finding citing authors from your %d publications' % len(all_publication_ids),
61
+ total=len(all_publication_ids)):
55
62
  all_citing_authors_nested.append(__citing_author_ids_from_publication(pub))
56
63
  all_citing_authors = list(itertools.chain(*all_citing_authors_nested))
57
64
 
65
+ # Find all citing insitutions from all citing authors.
58
66
  if num_processes > 1 and isinstance(num_processes, int):
59
- # Find all citing insitutions from all citing authors, in parallel.
60
67
  with Pool(processes=num_processes) as pool:
61
68
  author_institution_tuple_list = list(tqdm(pool.imap(__institutions_from_authors, all_citing_authors),
62
- desc='Finding all citing institutions...',
63
- total=len(all_citing_authors)))
69
+ desc='Finding citing institutions from %d citing authors' % len(all_citing_authors),
70
+ total=len(all_citing_authors)))
64
71
  else:
65
72
  author_institution_tuple_list = []
66
- for author in tqdm(all_citing_authors, desc='Finding all citing institutions...', total=len(all_citing_authors)):
73
+ for author in tqdm(all_citing_authors,
74
+ desc='Finding citing institutions from %d citing authors' % len(all_citing_authors),
75
+ total=len(all_citing_authors)):
67
76
  author_institution_tuple_list.append(__institutions_from_authors(author))
68
77
 
69
78
  return author_institution_tuple_list
70
79
 
71
80
  def clean_institution_names(author_institution_tuple_list: List[Tuple[str]]) -> List[Tuple[str]]:
72
81
  '''
73
- Step 3. Clean up the name of institutions from the author's self-entered Google Scholar affiliation.
82
+ Step 3. Clean up the names of institutions from the authors' affiliations on their Google Scholar profiles.
83
+ NOTE: This logic is very naive. Please send an issue or pull request if you have any idea how to improve it.
84
+ Currently we will not consider any paid service or tools that pose extra burden on the users, such as GPT API.
74
85
  '''
75
86
  cleaned_author_institution_tuple_list = []
76
87
  for author_name, institution_string in author_institution_tuple_list:
77
- # Use a regular expression to split the string by ',', ';', or 'and'.
78
- substring_list = [part.strip() for part in re.split(r'[;,]|\band\b', institution_string)]
88
+ # Use a regular expression to split the string by ';' or 'and'.
89
+ substring_list = [part.strip() for part in re.split(r'[;]|\band\b', institution_string)]
90
+ # Further split the substrings by ',' if the latter component is not a country.
91
+ substring_list = __country_aware_comma_split(substring_list)
92
+
79
93
  for substring in substring_list:
80
94
  # Use a regular expression to remove anything before 'at', or '@'.
81
95
  cleaned_institution = re.sub(r'.*?\bat\b|.*?@', '', substring, flags=re.IGNORECASE).strip()
@@ -83,29 +97,33 @@ def clean_institution_names(author_institution_tuple_list: List[Tuple[str]]) ->
83
97
  # a person's identity rather than affiliation.
84
98
  is_common_identity_string = re.search(
85
99
  re.compile(
86
- r'\b(director|manager|chair|engineer|programmer|scientist|professor|lecturer|phd|student|ph\.d|department of)\b',
100
+ r'\b(director|manager|chair|engineer|programmer|scientist|professor|lecturer|phd|ph\.d|postdoc|doctor|student|department of)\b',
87
101
  re.IGNORECASE),
88
102
  substring)
89
103
  if not is_common_identity_string:
90
104
  cleaned_author_institution_tuple_list.append((author_name, cleaned_institution))
91
105
  return cleaned_author_institution_tuple_list
92
106
 
93
- def institution_text_to_geocode(author_institution_tuple_list: List[Tuple[str]], num_processes: int = 32) -> List[Tuple[str]]:
107
+ def institution_text_to_geocode(author_institution_tuple_list: List[Tuple[str]], max_attempts: int = 3) -> List[Tuple[str]]:
94
108
  '''
95
109
  Step 4: Convert institutions in plain text to Geocode.
96
110
  '''
97
- if num_processes > 1 and isinstance(num_processes, int):
98
- with Pool(processes=num_processes) as pool:
99
- coordinates = list(tqdm(pool.imap(__text_to_geocode, author_institution_tuple_list),
100
- desc='Finding all geographic coordinates...',
101
- total=len(author_institution_tuple_list)))
102
- else:
103
- coordinates = []
104
- for author_institution_tuple in tqdm(author_institution_tuple_list,
105
- desc='Finding all geographic coordinates...',
106
- total=len(author_institution_tuple_list)):
107
- coordinates.append(__text_to_geocode(author_institution_tuple))
108
-
111
+ coordinates = []
112
+ # NOTE: According to the Nomatim Usage Policy (https://operations.osmfoundation.org/policies/nominatim/),
113
+ # we are explicitly asked not to submit bulk requests on multiple threads.
114
+ # Therefore, we will keep it to a loop instead of multiprocessing.
115
+ geolocator = Nominatim(user_agent='citation_mapper')
116
+ for author_institution_tuple in tqdm(author_institution_tuple_list,
117
+ desc='Finding geographic coordinates from %d citing institutions' % len(author_institution_tuple_list),
118
+ total=len(author_institution_tuple_list)):
119
+ for _ in range(max_attempts):
120
+ try:
121
+ author_name, institution_name = author_institution_tuple
122
+ geo_location = geolocator.geocode(institution_name)
123
+ if geo_location:
124
+ coordinates.append((geo_location.latitude, geo_location.longitude, author_name, institution_name))
125
+ except:
126
+ continue
109
127
  coordinates = [item for item in coordinates if item is not None] # Filter out empty coordinates.
110
128
  return coordinates
111
129
 
@@ -139,22 +157,38 @@ def __citing_author_ids_from_publication(cites_id: str):
139
157
  def __institutions_from_authors(author_id: str):
140
158
  time.sleep(random.uniform(1, 5)) # Random delay to reduce risk of being blocked.
141
159
  citing_author = scholarly.search_author_id(author_id)
142
- citing_author = scholarly.fill(citing_author)
143
160
  if 'affiliation' in citing_author:
144
161
  return (citing_author['name'], citing_author['affiliation'])
145
162
  return []
146
163
 
147
- def __text_to_geocode(author_institution_tuple, max_attempts: int = 3):
148
- geolocator = Nominatim(user_agent='citation_mapper')
149
- for _ in range(max_attempts):
150
- try:
151
- author_name, institution_name = author_institution_tuple
152
- geo_location = geolocator.geocode(institution_name)
153
- if geo_location:
154
- return (geo_location.latitude, geo_location.longitude, author_name, institution_name)
155
- except:
156
- pass
157
- return None
164
+ def __country_aware_comma_split(string_list: List[str]) -> List[str]:
165
+ comma_split_list = []
166
+
167
+ for part in string_list:
168
+ # Split the strings by comma.
169
+ # NOTE: The non-English comma is entered intentionally.
170
+ sub_parts = [sub_part.strip() for sub_part in re.split(r'[,,]', part)]
171
+ sub_parts_iter = iter(sub_parts)
172
+
173
+ # Merge the split strings if the latter component is a country name.
174
+ for sub_part in sub_parts_iter:
175
+ if __iscountry(sub_part):
176
+ continue # Skip country names if they appear as the first sub_part.
177
+ next_part = next(sub_parts_iter, None)
178
+ if __iscountry(next_part):
179
+ comma_split_list.append(f"{sub_part}, {next_part}")
180
+ else:
181
+ comma_split_list.append(sub_part)
182
+ if next_part:
183
+ comma_split_list.append(next_part)
184
+ return comma_split_list
185
+
186
+ def __iscountry(string: str) -> bool:
187
+ try:
188
+ pycountry.countries.lookup(string)
189
+ return True
190
+ except LookupError:
191
+ return False
158
192
 
159
193
  def generate_citation_map(scholar_id: str,
160
194
  output_path: str = 'citation_map.html',
@@ -201,20 +235,25 @@ def generate_citation_map(scholar_id: str,
201
235
  num_processes=num_processes)
202
236
  print('\nA total of %d citing institutions recorded.\n' % len(author_institution_tuple_list))
203
237
 
238
+ # Take unique tuples.
239
+ author_institution_tuple_list = list(set(author_institution_tuple_list))
240
+
204
241
  if print_citing_institutions:
205
242
  print('\nList of all citing authors and institutions before cleaning:\n')
206
- for item in sorted(author_institution_tuple_list):
243
+ for item in sorted((author_institution_tuple_list)):
207
244
  print(item)
208
245
 
209
246
  cleaned_author_institution_tuple_list = clean_institution_names(author_institution_tuple_list)
210
247
 
248
+ # Take unique tuples.
249
+ cleaned_author_institution_tuple_list = list(set(cleaned_author_institution_tuple_list))
250
+
211
251
  if print_citing_institutions:
212
252
  print('\nList of all citing authors and institutions after cleaning:\n')
213
253
  for item in sorted(cleaned_author_institution_tuple_list):
214
254
  print(item)
215
255
 
216
- coordinates = institution_text_to_geocode(author_institution_tuple_list + cleaned_author_institution_tuple_list,
217
- num_processes=num_processes)
256
+ coordinates = institution_text_to_geocode(author_institution_tuple_list + cleaned_author_institution_tuple_list)
218
257
  print('\nConverted the institutions to %d Geocodes.' % len(coordinates))
219
258
 
220
259
  citation_map = create_map(coordinates, pin_colorful=pin_colorful)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: citation-map
3
- Version: 3.7
3
+ Version: 3.9
4
4
  Summary: A simple tool to generate your Google Scholar citation world map.
5
5
  Home-page: https://github.com/ChenLiu-1996/CitationMap
6
6
  Author: Chen Liu
@@ -43,10 +43,6 @@ If you open it on a browser, you will see your own version of the following cita
43
43
 
44
44
  ## Usage
45
45
  1. Install the package.
46
- ```
47
- pip install citation-map
48
- ```
49
- Remember to upgrade to the latest version if you installed an earlier version.
50
46
  ```
51
47
  pip install citation-map --upgrade
52
48
  ```
@@ -91,8 +87,23 @@ If you open it on a browser, you will see your own version of the following cita
91
87
  If true, print the list of citing institutions (affiliations of citing authors).
92
88
  ```
93
89
 
90
+ ## Limitations
91
+
92
+ 1. This tool is purely based on Google Scholar. As a result, you are expected to have underestimations due to reasons such as:
93
+ - Your Google Scholar profile is not up-to-date.
94
+ - Some papers citing you are not indexed by Google Scholar.
95
+ - Some authors citing you do not have Google Scholar profile.
96
+ - Some authors citing you do not report their affiliations.
97
+ 2. `geopy.geocoders` is used to convert the citing authors' affiliations to geographic coordinates. To facilitate the process, I used some simple rule-based natural language processing to clean up the affiliations. As a result, you are expected to have:
98
+ - Underestimation if correct affiliations are not found by `geopy.geocoders`.
99
+ - Underestimation if we experience communication error with `geopy.geocoders`.
100
+ - Overestimation if non-affiliation terms are incorrectly identified as locations by `geopy.geocoders`.
101
+
102
+ **Please raise an issue or submit a pull request if you have some good idea to better process the affiliation string. Note that currently I am not considering any paid service or tools that pose extra burden on the users, such as GPT API.**
103
+
104
+
94
105
  ## Debug
95
- 1. `scholarly._proxy_generator.MaxTriesExceededException` or `[WARNING!] Blocked by CAPTCHA or robot check`
106
+ 1. `MaxTriesExceededException` or `[WARNING!] Blocked by CAPTCHA or robot check`
96
107
 
97
108
  - From my experience, both are good indicators that your IP address is blocked by Google Scholar due to excessive crawling (using the `scholarly` package).
98
109
  - One hot fix I found was to hop on a University VPN and run again. I typically experience this error after running the tool twice, and I need to disconnect and reconnect my VPN to "unblock" myself.
@@ -100,33 +111,49 @@ If you open it on a browser, you will see your own version of the following cita
100
111
 
101
112
  ## Changelog
102
113
 
103
- ### Version 3.7
104
- I updated the logic for webscraping and avoided using `scholarly.citeby()` which is the biggest trigger of blacklisting from Google Scholar.
114
+ <details>
115
+ <summary>Version 3.9</summary>
116
+ <br>
117
+ In 3.9, I slightly improved the logic for affiliation extraction.
118
+
119
+ In 3.8, I removed multiprocessing for `geopy.geocoders` as per their official documentation. Also I cleaned up some unnecessary `scholarly` calls which further helps us not get blacklisted by Google Scholar.
120
+
121
+ In 3.7, I updated the logic for webscraping and avoided using `scholarly.citeby()` which is the biggest trigger of blacklisting from Google Scholar.
105
122
 
106
- **Now we should be able to handle users with more citations than before. I tested on a profile with 400 citations without running into any issue.**
123
+ **Now we should be able to handle users with more citations than before. I tested on a profile with 1000 citations without running into any issue.**
124
+ </details>
107
125
 
108
- ### Version 3.0
109
- I realized a problem with how I used `geopy.geocoders`. A majority of the authors' self-entered affiliations include details that are irrelevant to the affiliation itself. Therefore, they are not successfully found in the system and hence are not converted to geographic coordinates on the world map.
126
+ <details>
127
+ <summary>Version 3.0</summary>
128
+ <br>
129
+ I realized a problem with how I used `geopy.geocoders`. A majority of the authors' affiliations include details that are irrelevant to the affiliation itself. Therefore, they are not successfully found in the system and hence are not converted to geographic coordinates on the world map.
110
130
 
111
131
  For example, we would want the substring "Yale University" from the string "Assistant Professor at Yale University".
112
132
 
113
133
  I applied a simple fix with some rule-based natural language processing. This helps us identify many missing citing locations.
114
134
 
115
135
  **Please raise an issue or submit a pull request if you have some good idea to better process the affiliation string. Note that currently I am not considering any paid service or tools that pose extra burden on the users, such as GPT API.**
136
+ </details>
116
137
 
117
- ### Version 2.0
138
+ <details>
139
+ <summary>Version 2.0</summary>
140
+ <br>
118
141
  I finally managed to **drastically speed up** the process using multiprocessing, in a way that avoids being blocked by Google Scholar.
119
142
 
120
143
  On my personal computer, processing my profile with 100 citations took 1 hour with version 1.0 while it's now taking 5 minutes with version 2.0.
121
144
 
122
145
  With that said, please be careful and do not run this tool frequently. I can easily get on Google Scholar's blacklist after a few runs.
146
+ </details>
123
147
 
124
- ### Version 1.0
148
+ <details>
149
+ <summary>Version 1.0</summary>
150
+ <br>
125
151
  Very basic functionality.
126
152
 
127
153
  This script is a bit slow. On my personal computer, it takes half a minute to process each citation. If you have thousands of citations, it may or may not be a good idea to use this script.
128
154
 
129
155
  I tried to use multiprocessing, but unfortunately the excessive visits get me blocked by Google Scholar.
156
+ </details>
130
157
 
131
158
  ## Dependencies
132
159
  Dependencies (`scholarly`, `geopy`, `folium`, `tqdm`) are already taken care of when you install via pip.
@@ -5,7 +5,7 @@ with open('README.md') as f:
5
5
 
6
6
  setup(
7
7
  name='citation-map',
8
- version='3.7',
8
+ version='3.9',
9
9
  license='Apache 2.0',
10
10
  author='Chen Liu',
11
11
  author_email='chen.liu.cl2482@yale.edu',
File without changes
@@ -1,8 +1,8 @@
1
+ import random
1
2
  import requests
3
+ import time
2
4
  from bs4 import BeautifulSoup
3
5
  from typing import List
4
- import time
5
- import random
6
6
 
7
7
 
8
8
  def get_author_ids(paper_url: str) -> List[str]:
File without changes