search_google_scholar.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. #!/usr/bin/env python3
  2. """
  3. Google Scholar Search Tool
  4. Search Google Scholar and export results.
  5. Note: This script requires the 'scholarly' library.
  6. Install with: pip install scholarly
  7. """
  8. import sys
  9. import argparse
  10. import json
  11. import time
  12. import random
  13. from typing import List, Dict, Optional
  14. try:
  15. from scholarly import scholarly, ProxyGenerator
  16. SCHOLARLY_AVAILABLE = True
  17. except ImportError:
  18. SCHOLARLY_AVAILABLE = False
  19. print('Warning: scholarly library not installed. Install with: pip install scholarly', file=sys.stderr)
  20. class GoogleScholarSearcher:
  21. """Search Google Scholar using scholarly library."""
  22. def __init__(self, use_proxy: bool = False):
  23. """
  24. Initialize searcher.
  25. Args:
  26. use_proxy: Use free proxy (helps avoid rate limiting)
  27. """
  28. if not SCHOLARLY_AVAILABLE:
  29. raise ImportError('scholarly library required. Install with: pip install scholarly')
  30. # Setup proxy if requested
  31. if use_proxy:
  32. try:
  33. pg = ProxyGenerator()
  34. pg.FreeProxies()
  35. scholarly.use_proxy(pg)
  36. print('Using free proxy', file=sys.stderr)
  37. except Exception as e:
  38. print(f'Warning: Could not setup proxy: {e}', file=sys.stderr)
  39. def search(self, query: str, max_results: int = 50,
  40. year_start: Optional[int] = None, year_end: Optional[int] = None,
  41. sort_by: str = 'relevance') -> List[Dict]:
  42. """
  43. Search Google Scholar.
  44. Args:
  45. query: Search query
  46. max_results: Maximum number of results
  47. year_start: Start year filter
  48. year_end: End year filter
  49. sort_by: Sort order ('relevance' or 'citations')
  50. Returns:
  51. List of result dictionaries
  52. """
  53. if not SCHOLARLY_AVAILABLE:
  54. print('Error: scholarly library not installed', file=sys.stderr)
  55. return []
  56. print(f'Searching Google Scholar: {query}', file=sys.stderr)
  57. print(f'Max results: {max_results}', file=sys.stderr)
  58. results = []
  59. try:
  60. # Perform search
  61. search_query = scholarly.search_pubs(query)
  62. for i, result in enumerate(search_query):
  63. if i >= max_results:
  64. break
  65. print(f'Retrieved {i+1}/{max_results}', file=sys.stderr)
  66. # Extract metadata
  67. metadata = {
  68. 'title': result.get('bib', {}).get('title', ''),
  69. 'authors': ', '.join(result.get('bib', {}).get('author', [])),
  70. 'year': result.get('bib', {}).get('pub_year', ''),
  71. 'venue': result.get('bib', {}).get('venue', ''),
  72. 'abstract': result.get('bib', {}).get('abstract', ''),
  73. 'citations': result.get('num_citations', 0),
  74. 'url': result.get('pub_url', ''),
  75. 'eprint_url': result.get('eprint_url', ''),
  76. }
  77. # Filter by year
  78. if year_start or year_end:
  79. try:
  80. pub_year = int(metadata['year']) if metadata['year'] else 0
  81. if year_start and pub_year < year_start:
  82. continue
  83. if year_end and pub_year > year_end:
  84. continue
  85. except ValueError:
  86. pass
  87. results.append(metadata)
  88. # Rate limiting to avoid blocking
  89. time.sleep(random.uniform(2, 5))
  90. except Exception as e:
  91. print(f'Error during search: {e}', file=sys.stderr)
  92. # Sort if requested
  93. if sort_by == 'citations' and results:
  94. results.sort(key=lambda x: x.get('citations', 0), reverse=True)
  95. return results
  96. def metadata_to_bibtex(self, metadata: Dict) -> str:
  97. """Convert metadata to BibTeX format."""
  98. # Generate citation key
  99. if metadata.get('authors'):
  100. first_author = metadata['authors'].split(',')[0].strip()
  101. last_name = first_author.split()[-1] if first_author else 'Unknown'
  102. else:
  103. last_name = 'Unknown'
  104. year = metadata.get('year', 'XXXX')
  105. # Get keyword from title
  106. import re
  107. title = metadata.get('title', '')
  108. words = re.findall(r'\b[a-zA-Z]{4,}\b', title)
  109. keyword = words[0].lower() if words else 'paper'
  110. citation_key = f'{last_name}{year}{keyword}'
  111. # Determine entry type (guess based on venue)
  112. venue = metadata.get('venue', '').lower()
  113. if 'proceedings' in venue or 'conference' in venue:
  114. entry_type = 'inproceedings'
  115. venue_field = 'booktitle'
  116. else:
  117. entry_type = 'article'
  118. venue_field = 'journal'
  119. # Build BibTeX
  120. lines = [f'@{entry_type}{{{citation_key},']
  121. # Convert authors format
  122. if metadata.get('authors'):
  123. authors = metadata['authors'].replace(',', ' and')
  124. lines.append(f' author = {{{authors}}},')
  125. if metadata.get('title'):
  126. lines.append(f' title = {{{metadata["title"]}}},')
  127. if metadata.get('venue'):
  128. lines.append(f' {venue_field} = {{{metadata["venue"]}}},')
  129. if metadata.get('year'):
  130. lines.append(f' year = {{{metadata["year"]}}},')
  131. if metadata.get('url'):
  132. lines.append(f' url = {{{metadata["url"]}}},')
  133. if metadata.get('citations'):
  134. lines.append(f' note = {{Cited by: {metadata["citations"]}}},')
  135. # Remove trailing comma
  136. if lines[-1].endswith(','):
  137. lines[-1] = lines[-1][:-1]
  138. lines.append('}')
  139. return '\n'.join(lines)
  140. def main():
  141. """Command-line interface."""
  142. parser = argparse.ArgumentParser(
  143. description='Search Google Scholar (requires scholarly library)',
  144. epilog='Example: python search_google_scholar.py "machine learning" --limit 50'
  145. )
  146. parser.add_argument(
  147. 'query',
  148. help='Search query'
  149. )
  150. parser.add_argument(
  151. '--limit',
  152. type=int,
  153. default=50,
  154. help='Maximum number of results (default: 50)'
  155. )
  156. parser.add_argument(
  157. '--year-start',
  158. type=int,
  159. help='Start year for filtering'
  160. )
  161. parser.add_argument(
  162. '--year-end',
  163. type=int,
  164. help='End year for filtering'
  165. )
  166. parser.add_argument(
  167. '--sort-by',
  168. choices=['relevance', 'citations'],
  169. default='relevance',
  170. help='Sort order (default: relevance)'
  171. )
  172. parser.add_argument(
  173. '--use-proxy',
  174. action='store_true',
  175. help='Use free proxy to avoid rate limiting'
  176. )
  177. parser.add_argument(
  178. '-o', '--output',
  179. help='Output file (default: stdout)'
  180. )
  181. parser.add_argument(
  182. '--format',
  183. choices=['json', 'bibtex'],
  184. default='json',
  185. help='Output format (default: json)'
  186. )
  187. args = parser.parse_args()
  188. if not SCHOLARLY_AVAILABLE:
  189. print('\nError: scholarly library not installed', file=sys.stderr)
  190. print('Install with: pip install scholarly', file=sys.stderr)
  191. print('\nAlternatively, use PubMed search for biomedical literature:', file=sys.stderr)
  192. print(' python search_pubmed.py "your query"', file=sys.stderr)
  193. sys.exit(1)
  194. # Search
  195. searcher = GoogleScholarSearcher(use_proxy=args.use_proxy)
  196. results = searcher.search(
  197. args.query,
  198. max_results=args.limit,
  199. year_start=args.year_start,
  200. year_end=args.year_end,
  201. sort_by=args.sort_by
  202. )
  203. if not results:
  204. print('No results found', file=sys.stderr)
  205. sys.exit(1)
  206. # Format output
  207. if args.format == 'json':
  208. output = json.dumps({
  209. 'query': args.query,
  210. 'count': len(results),
  211. 'results': results
  212. }, indent=2)
  213. else: # bibtex
  214. bibtex_entries = [searcher.metadata_to_bibtex(r) for r in results]
  215. output = '\n\n'.join(bibtex_entries) + '\n'
  216. # Write output
  217. if args.output:
  218. with open(args.output, 'w', encoding='utf-8') as f:
  219. f.write(output)
  220. print(f'Wrote {len(results)} results to {args.output}', file=sys.stderr)
  221. else:
  222. print(output)
  223. print(f'\nRetrieved {len(results)} results', file=sys.stderr)
  224. if __name__ == '__main__':
  225. main()