Создание агрегаторов научных статей

от автора

Всем привет! В этом посте я хочу рассказать про некоторые возможности автоматизации задач, с которыми сталкивается программист-исследователь.

В моей работе часто возникает потребность изучать научные статьи. Это помогает быть в курсе событий, а также исследовать новые методы и области.

Бывает очень полезно фиксировать прочитанную информацию, чтобы не приходилось постоянно возвращаться к одному и тому же материалу, чтобы что-то найти. Обычно я веду заметки, но переносить туда ссылки на статьи, скачивать их, создавать таблицы бывает муторно. Огромную работу проделали создатели гитхаб репозиториев-агрегаторов статей по определённым темам. Например, статьи про клоны кода или статьи про фаззинг. Эти репозитории заставили меня задуматься над автоматизацией создания таких списков статей. В результате я определила последовательность действий, которую я выполняю при исследовании новой темы:

  • Шаг 1. Поиск статей в известных источниках (например, Semantic Scholar или arxiv)

  • Шаг 2. Чтение аннотаций к статьям, по которым часто можно понять, подходят ли они по тематике.

  • Шаг 3. Выделение важных объектов из статей: к ним можно отнести ссылки на гитхаб, графики, картинки (чтобы можно было, например, сослаться на результаты статьи в своей презентации, не испортив качество объекта при вставке) , а также год написания и журнал для понимания уровня статьи и актуальности.

  • Шаг 4. Подробное изучение выбранных статей.

Сбор информации для первых трёх шагов можно автоматизировать. Остановимся подробнее на них.

Поиск статей

Semantic Scholar и arxiv имеют специальное API для поиска статей по запросу. С помощью него можно узнать базовую информацию про статьи и скачать их при наличии открытого доступа.

Например, исходя из документации для arxiv, для поиска статей по запросу потребуется написать подобный код:

import arxiv  # Construct the default API client. client = arxiv.Client()  # Search for the 10 most recent articles matching the keyword "quantum." search = arxiv.Search(   query = "quantum",   max_results = 10,   sort_by = arxiv.SortCriterion.SubmittedDate ) results = client.results(search)

Для найденных статей можно получить аннотацию, авторов, год публикации, ссылку на скачивание в случае открытого доступа и т.д:

papers = [     {           "title": result.title,           "authors": ", ".join([author.name for author in result.authors]),           "journal": result.journal_ref,           "year": result.published.year,           "abstract": result.summary,           "openAccessPdf": {"url": result.pdf_url},           "paperId": result.entry_id.split("/")[-1], # last part of url after slash     } for result in results ]

Статьи можно скачать с помощью следующего кода:

results = client.results(search) paper_to_download = next(results) paper_to_download.download_pdf(dirpath=".", filename="some-article.pdf")

Для Semantic Scholar примеры использования API есть в репозитории.

Аналогичный по содержанию код для Semantic Scholar API может выглядеть следующим образом:

import requests import json from datetime import datetime  x_api_key = "your-api-key" # you can get it from official site  class SemanticScholarClient:     def __init__(self):         self.base_url = "https://api.semanticscholar.org/graph/v1"      def search(self, query, max_results=10, sort_by="relevance"):         endpoint = f"{self.base_url}/paper/search"                  params = {             "query": query,             "limit": max_results,             "fields": "paperId,title,isOpenAccess,openAccessPdf,abstract,year,authors,citationCount,url,venue",             "sort": sort_by         }          headers = {             "X-API-KEY": x_api_key         }          response = requests.get(endpoint, params=params, headers=headers)         response.raise_for_status()         return response.json()['data']  class SemanticScholarSearch:     def __init__(self, query, max_results=10, sort_by="relevance"):         self.query = query         self.max_results = max_results         self.sort_by = sort_by  # Usage client = SemanticScholarClient()  # Search for the 10 most recent articles matching the keyword "quantum." search = SemanticScholarSearch(     query="quantum",     max_results=10,     sort_by="publicationDate:desc"  # Sort by publication date, descending ) results = client.search(search.query, search.max_results, search.sort_by)  papers = [     {         "title": result['title'],         "authors": ", ".join([author['name'] for author in result['authors']]),         "journal": result.get('venue', ''),         "year": result['year'],         "abstract": result.get('abstract', ''),         "openAccessPdf": {"url": result.get('openAccessPdf', {}).get('url', '')} if result.get('openAccessPdf', {}) else None,         "paperId": result['paperId'],     } for result in results if result ]   # Download papers for paper in papers:     if paper['openAccessPdf']:         print(f"Title: {paper['title']}")         response = requests.get(paper['openAccessPdf']['url'])         response.raise_for_status()          filepath = f"{paper['title']}.pdf"          with open(filepath, 'wb') as f:             f.write(response.content)

Выделение объектов

Для автоматизации выделения объектов из статьи можно использовать библиотеку pdfplumber. В написании кода с использованием этой библиотеки очень помогла статья. С помощью парсинга элементов в PDF и регулярных выражений можно извлекать существующие в статье ссылки на гитхаб:

import pdfplumber import re  def extract_text_from_pdf(pdf):       text = ""       for page in pdf.pages:           text += page.extract_text() + "\n"       return text.strip()  pdf = pdfplumber.open("some-article.pdf") text_from_pdf = extract_text_from_pdf(pdf)  github_regex = r"https?://(?:www\.)?github\.com/[\w-]+/[\w.-]+[\w-]" github_links = re.findall(github_regex, text_from_pdf)

Для извлечения картинок подходит библиотека PyMuPDF. Вот подробный гайд про использование библиотеки. Для сохранения картинок из статьи можно написать следующий код:

import fitz doc = fitz.Document("some-article.pdf")  for i in tqdm(range(len(doc)), desc="pages"):     cnt = 0 # counter to divide images on one page     for img in tqdm(doc.get_page_images(i), desc="page_images"):         xref = img[0]         image = doc.extract_image(xref)         pix = fitz.Pixmap(doc, xref)         pix.save("img_%s_%s.png" % (i, cnt))         cnt += 1

Не во всех случаях указанным методом удаётся извлечь все изображения из статей, поэтому в этой задаче есть огромное пространство для размышлений и идей по улучшению метода.


Перечисленные этапы реализованы в репозитории Researcher-Helper. На данный момент в нём поддерживается поиск по Semantic Scholar и arxiv. В результате он позволяет сгенерировать по запросу таблицу со статьями в формате html. Буду рада обратной связи, идеям по улучшению и советам в реализации описанных этапов!


ссылка на оригинал статьи https://habr.com/ru/articles/846704/