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.
- {citation-map-3.7 → citation-map-3.9}/PKG-INFO +40 -13
- {citation-map-3.7 → citation-map-3.9}/README.md +39 -12
- {citation-map-3.7 → citation-map-3.9}/citation_map/citation_map.py +88 -49
- {citation-map-3.7 → citation-map-3.9}/citation_map.egg-info/PKG-INFO +40 -13
- {citation-map-3.7 → citation-map-3.9}/setup.py +1 -1
- {citation-map-3.7 → citation-map-3.9}/LICENSE.md +0 -0
- {citation-map-3.7 → citation-map-3.9}/citation_map/__init__.py +0 -0
- {citation-map-3.7 → citation-map-3.9}/citation_map/scholarly_support.py +2 -2
- {citation-map-3.7 → citation-map-3.9}/citation_map.egg-info/SOURCES.txt +0 -0
- {citation-map-3.7 → citation-map-3.9}/citation_map.egg-info/dependency_links.txt +0 -0
- {citation-map-3.7 → citation-map-3.9}/citation_map.egg-info/requires.txt +0 -0
- {citation-map-3.7 → citation-map-3.9}/citation_map.egg-info/top_level.txt +0 -0
- {citation-map-3.7 → citation-map-3.9}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: citation-map
|
|
3
|
-
Version: 3.
|
|
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. `
|
|
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
|
-
|
|
104
|
-
|
|
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
|
|
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
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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. `
|
|
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
|
-
|
|
88
|
-
|
|
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
|
|
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
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
12
|
-
from
|
|
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.
|
|
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
|
-
|
|
34
|
-
|
|
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,
|
|
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
|
|
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,
|
|
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
|
-
|
|
63
|
-
|
|
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,
|
|
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
|
|
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 '
|
|
78
|
-
substring_list = [part.strip() for part in re.split(r'[
|
|
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|
|
|
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]],
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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.
|
|
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. `
|
|
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
|
-
|
|
104
|
-
|
|
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
|
|
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
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|