Соскоб ResearchGate всех членов института на Python

  • Что будет соскоблено
  • Предварительные условия
  • Полный код
    • Извлечение данных из строки JSON
  • Ссылки

Что будет соскоблено

Необходимые условия

Базовые знания о скраппинге с помощью селекторов CSS

CSS-селекторы определяют, к какой части разметки применяется тот или иной стиль, что позволяет извлекать данные из соответствующих тегов и атрибутов.

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

Отдельная виртуальная среда

Вкратце, это вещь, которая создает независимый набор установленных библиотек, включая различные версии Python, которые могут сосуществовать друг с другом в одной системе, предотвращая таким образом конфликты библиотек или версий Python.

Если вы раньше не работали с виртуальным окружением, посмотрите специализированный учебник по виртуальным окружениям Python с использованием Virtualenv и статью в моем блоге Poetry, чтобы ознакомиться с ними.

📌Примечание: это не является строгим требованием для данного поста в блоге.

Установите библиотеки:

pip install parsel playwright
Войдите в полноэкранный режим Выйти из полноэкранного режима

Снизить вероятность блокировки

Существует вероятность того, что запрос может быть заблокирован. Посмотрите, как уменьшить вероятность блокировки при веб-скрепинге, существует одиннадцать методов обхода блокировки большинства сайтов.

Полный код

from parsel import Selector
from playwright.sync_api import sync_playwright
import re, json, time


def scrape_institution_members(institution: str):
    with sync_playwright() as p:

        institution_memebers = []
        page_num = 1 

        members_is_present = True
        while members_is_present:

            browser = p.chromium.launch(headless=True, slow_mo=50)
            page = browser.new_page()
            page.goto(f"https://www.researchgate.net/institution/{institution}/members/{page_num}")
            selector = Selector(text=page.content())

            print(f"page number: {page_num}")

            for member in selector.css(".nova-legacy-v-person-list-item"):
                name = member.css(".nova-legacy-v-person-list-item__align-content a::text").get()
                link = f'https://www.researchgate.net{member.css(".nova-legacy-v-person-list-item__align-content a::attr(href)").get()}'
                profile_photo = member.css(".nova-legacy-l-flex__item img::attr(src)").get()
                department = member.css(".nova-legacy-v-person-list-item__stack-item:nth-child(2) span::text").get()
                desciplines = member.css("span .nova-legacy-e-link::text").getall()

                institution_memebers.append({
                    "name": name,
                    "link": link,
                    "profile_photo": profile_photo,
                    "department": department,
                    "descipline": desciplines
                })

            # check for Page not found selector
            if selector.css(".headline::text").get():
                members_is_present = False
            else:
                time.sleep(2) # use proxies and captcha solver instead of this
                page_num += 1 # increment a one. Pagination

        print(json.dumps(institution_memebers, indent=2, ensure_ascii=False))
        print(len(institution_memebers)) # 624 from a EM-Normandie-Business-School

        browser.close()

        """
        you can also render the page and extract data from the inline JSON string,
        however, it's messy and from my perspective, it is easier to scrape the page directly.
        """

        # https://regex101.com/r/8qjfnH/1
        # extracted_data = re.findall(r"s+RGCommons.react.mountWidgetTree(({"data":{"menu".*:true}));;",
        #                        str(page.content()))[0]
        # json_data = json.loads(extracted_data)
        # print(json_data)

scrape_institution_members(institution="EM-Normandie-Business-School")
Вход в полноэкранный режим Выход из полноэкранного режима

Объяснение кода

Импорт библиотек:

from parsel import Selector
from playwright.sync_api import sync_playwright
import re, json, time
Войти в полноэкранный режим Выйти из полноэкранного режима
Код Пояснение
parsel для разбора документов HTML/XML. Поддерживает XPath.
playwright для рендеринга страницы с помощью экземпляра браузера.
re для сопоставления частей данных с регулярным выражением.
json для преобразования словаря Python в строку JSON.
time не является практичным способом обхода блоков запросов. Вместо этого используйте proxy/captcha solver.

Определите функцию:

def scrape_institution_members(institution: str):
    # ...
Войти в полноэкранный режим Выйти из полноэкранного режима
Код Пояснение
institution: str чтобы сообщить Python, что institution должен быть str.

Откройте playwright с помощью контекстного менеджера:

with sync_playwright() as p:
    # ...
Войти в полноэкранный режим Выйти из полноэкранного режима

Запустить экземпляр браузера, открыть и goto страницу и передать ответ парсеру HTML/XML:

browser = p.chromium.launch(headless=True, slow_mo=50)
page = browser.new_page()
page.goto(f"https://www.researchgate.net/institution/{institution}/members/{page_num}")
selector = Selector(text=page.content())
Войти в полноэкранный режим Выход из полноэкранного режима
Код Пояснение
p.chromium.launch() запустить экземпляр браузера Chromium.
headless явно указать playwright на запуск в безголовом режиме, даже если это значение является дефолтным.
slow_mo указать playwright на замедление выполнения.
browser.new_page() открыть новую страницу.

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

institution_memebers = []
page_num = 1

members_is_present = True
while members_is_present:

      # extraction code

      # check for Page not found selector
      if selector.css(".headline::text").get():
          members_is_present = False
      else:
          time.sleep(2) # use proxies and captcha solver instead of this
          page_num += 1 # increment a one. Pagination
Войти в полноэкранный режим Выход из полноэкранного режима

Итерация результатов членов на каждой странице, извлечение данных и append во временный список:

for member in selector.css(".nova-legacy-v-person-list-item"):
    name = member.css(".nova-legacy-v-person-list-item__align-content a::text").get()
    link = f'https://www.researchgate.net{member.css(".nova-legacy-v-person-list-item__align-content a::attr(href)").get()}'
    profile_photo = member.css(".nova-legacy-l-flex__item img::attr(src)").get()
    department = member.css(".nova-legacy-v-person-list-item__stack-item:nth-child(2) span::text").get()
    desciplines = member.css("span .nova-legacy-e-link::text").getall()

    institution_memebers.append({
        "name": name,
        "link": link,
        "profile_photo": profile_photo,
        "department": department,
        "descipline": desciplines
    })
Вход в полноэкранный режим Выйти из полноэкранного режима
Код Пояснение
css() для разбора данных из переданного селектора(ов) CSS. Каждый CSS-запрос траслируется в XPath с помощью пакета csselect под капотом.
извлечение текстовых или атрибутивных данных из узла.
получить фактические данные из сопоставленного узла или получить список сопоставленных данных из узлов.

Вывести извлеченные данные, length извлеченных членов и close экземпляра браузера:

print(json.dumps(institution_memebers, indent=2, ensure_ascii=False))
print(len(institution_memebers)) # 624 from a EM-Normandie-Business-School

browser.close()
Войти в полноэкранный режим Выход из полноэкранного режима

Часть вывода JSON (первый результат — первый член, последний — последний член):

[
  {
    "name": "Sylvaine Castellano",
    "link": "https://www.researchgate.netprofile/Sylvaine-Castellano",
    "profile_photo": "https://i1.rgstatic.net/ii/profile.image/341867548954625-1458518983237_Q64/Sylvaine-Castellano.jpg",
    "department": "EM Normandie Business School",
    "descipline": [
      "Sustainable Development",
      "Sustainability",
      "Innovation"
    ]
  }, ... other results
  {
    "name": "Constance Biron",
    "link": "https://www.researchgate.netprofile/Constance-Biron-3",
    "profile_photo": "https://c5.rgstatic.net/m/4671872220764/images/template/default/profile/profile_default_m.jpg",
    "department": "Marketing",
    "descipline": []
  }
]
Войти в полноэкранный режим Выход из полноэкранного режима

Извлечение данных из строки JSON

Вы можете извлечь данные без использования parsel, распечатав данные страницы content(), которая получит полное HTML-содержимое страницы, включая doctype, и разобрав данные с помощью регулярного выражения.

Я показываю и этот вариант, просто на случай, если кто-то из вас предпочитает этот подход прямому разбору страницы.

# https://regex101.com/r/8qjfnH/1
extracted_data = re.findall(r"s+RGCommons.react.mountWidgetTree(({"data":{"menu".*:true}));;",
                       str(page.content()))[0]
json_data = json.loads(extracted_data)
print(json_data)
Войти в полноэкранный режим Выход из полноэкранного режима

Выходные данные:

Вот в чем дело, извлечение данных из строки JSON кажется практически более убедительным, но давайте рассмотрим пример доступа к ключу fullName:

                                     👇👇👇👇👇
initState.rigel.store.account(id:\"AC:2176142\").fullName
Вход в полноэкранный режим Выйти из полноэкранного режима

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


Ссылки

  • Файл в репозитории GitHub

Присоединяйтесь к нам на Twitter | YouTube

Add a Feature Request💫 or a Bug🐞

🌼

Оцените статью
Procodings.ru
Добавить комментарий