Статья Поиск, скачивание и извлечение метаданных из документов в свободном доступе с помощью Python. #01

В широком понимании метаданных, все уже привыкли к тому, что метаданные есть в фото, аудио или видео. Но мы редко задумываемся о том, что метаданные есть и у других типов файлов. Например, у различных форматов офисных документов. И содержаться в них может имя учетной записи, который этот документ создал, иногда электронная почта или ФИО, реже телефон. И если с фото более-менее все почти наладилось и очисткой метаданных занимаются сами сайты, куда вы его загружаете, если это не стоковое изображение, конечно, то вот с документами несколько иная картина. Редко кто заморачивется над тем, чтобы удалить метаданные из документа, перед тем, как его загрузить на сайт. А вот о том, как получить документы с нужного сайта и извлечь эти метаданные с помощью Python давайте и поговорим.

01.jpg


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


Предыстория. Что, зачем и почему

Существует такой скрипт, называется Metagoofil, который скачивает проиндексированные документы из поиска Google и извлекает метаданные. Версий данного скрипта две, от разных разработчиков. Одна только скачивает, вторая скачивает и извлекает. Вот ими я и, скажем так, вдохновился. И попытался сделать что-то подобное. Насколько хорошо это получилось, судить вам. Но учитывая, что я учусь программировать на Python, думаю, что можно сделать скидку )))

Данный функционал может понадобиться осинтерам при изучении данных организаций, которые выкладывают документы на сайта просто пачками. С помощью скрипта можно узнать авторов документов; софт, который использовался при создании, а также адреса электронной почты, если они содержаться в тексте документа. Все документы, которые собирает скрипт находятся в открытом доступе, а значит их загрузка разрешена по умолчанию. Что ж, хватит слов, пора представить вам мой «велосипед».


Что понадобиться?

В этот раз понадобиться довольно много. Сам не ожидал, что придется использовать столько библиотек. Хотя, может быть это и не много, не знаю. Итак, для начала нужно установить основную библиотеку, с помощью которой будет выполняться поиск документов в индексе Google, с использованием расширенных запросов, то есть, так называемых Google Dorks. Библиотека называется google. Пишем в терминале:

pip install google

Следующими кандидатами на установку будут сразу три библиотеки: bs4, lxml и requests. Первая служит для поиска данных в html или xml. lxml — это собственно парсер. И библиотека requests служит для выполнения запросов к сайтам, получения от них информации, загрузки файлов, авторизации и многого другого. Ну и urllib3. На самом деле, эта библиотека не особо нужна. Но, для того, чтобы подавить предупреждения от requests при выполнении запросов без верификации, ее нужно установить. Для установки пишем:

pip install bs4 requests lxml urllib3

Теперь давайте установим библиотеку fake-useragent. На самом деле, если я правильно понимаю, это оболочка для скрипта, который просто в случайном порядке выдает user-agent для сайтов из составленного ранее списка. И хоститься данный скрипт на heroku, потому как в процессе тестирования моего скрипта fake-useragent несколько раз выпадал в осадок. И там было ясно написано, где он располагается. Ну, суть не в этом. Главное, что он со своей задачей справляется, а именно — предоставление случайного user-agent. Для установки пишем в терминале:

pip install fake-useragent

Теперь установим selenium. Это очень мощное средство для автоматизации тестирования веб-приложений с помощью браузера. Но, в данном случае, с его помощью мы будем тестировать поисковик DuckDuckGo. Зачем, поймете в процессе. А пока, для установки пишем:

pip install selenium

А теперь давайте попробуем скопом установить все библиотеки, которые будут нужны для извлечения метаданных из документов. Для начала — Pillow. С помощью данной библиотеки мы будем извлекать метаданные из изображений, если таковые будут присутствовать в документе, в смысле изображения и, если у этих изображений есть метаданные. Следующая библиотека — olefile. С ее помощью мы извлечем метаданные из старого формата документов Microsoft Office, таких как doc, ppt, xls. Затем pymupdf. С его помощью мы будем извлекать метаданные из документов pdf, а также текст, для поиска в нем нужной информации. Ну и python-docx. В принципе, если вы не хотите сохранять документы в формате Word, можно эту библиотеку не ставить, а из моего скрипта удалить строки, в которых данные сохраняются. И оставить только текстовый формат. Но, я решил оставить. Тем более, что при необходимости Word можно по-быстрому пересохранить в PDF. Пишем в терминале:

pip install Pillow olefile pymupdf python-docx

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

Python:
import os
import platform
import re
import threading
import time
import urllib
import zipfile
from pathlib import Path
from xml.etree import ElementTree as etree

import docx
import fitz
import olefile
import requests
import selenium
from PIL import Image, ExifTags
from bs4 import BeautifulSoup
from docx.shared import Pt
from fake_useragent import UserAgent
from googlesearch import search
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.firefox.service import Service

Ну и сразу же пишем строчку, которая подавит warnings от библиотеки requests:

requests.packages.urllib3.disable_warnings()

И объявим три глобальных множества, в которые будем сохранять найденные в процессе работы скрипта авторов документов, ПО, которое использовалось при их создании и адреса электронной почты:

Python:
author = set()
soft = set()
email = set()

Так же, нужно сделать папку geckodriver, в которой должны лежать драйвера для запуска браузера Firefox. Google Chrome здесь не использую. Но, если вы захотите переписать под него, то нужно будет использовать другие драйвера. Скачать драйвера для Firefox можно на следующем сайте. Здесь ищите версию для вашего браузера и операционной системы. Я при написании скрипта пользовался Linux, но, тем не менее, в сам скрипт добавил определение версии ОС и соответствующую загрузку драйвера. Главное, чтобы они, драйвера, лежали в нужных папках.


Поиск документов в Google

Для поиска документов будем использовать библиотеку google и так называемые Google Dorks, а точнее совсем небольшую их часть, а именно, для примера: «site:example.com filetype:pdf». Создадим функцию search_g(site: str, col: int, doc: str). Здесь на вход передаются следующие параметры: адрес сайта, на котором нужно производить поиск, лучше без http или https. Количество документов для поиска, то есть, если вам нужно 50, он найдет 50, даже если есть больше. И формат документов для поиска, например pdf.

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

Создадим цикл, в котором будем перебирать найденные результаты в поисковом запросе search. В него передаем сформированный ранее запрос, регион для поиска, язык поиска, количество результатов на странице, первый результат для извлечения, последний результат. И если последний результат не указывать, поиск будет продолжаться, пока есть документы. Интервал ожидания между запросами. Здесь стоит две секунды. Но, здесь нужно подумать. Так как слишком маленький интервал может привести к тому, что Google вас заблокирует, слишком большой замедлит поиск. Также передаются дополнительные параметры. В данном случае, передается значение, которое указывает, чтобы Google не фильтровал результаты поиска.

Python:
def search_g(site: str, col: int, doc: str):
    dork = f"site:{site} filetype:{doc}"
    res_list = []
    try:
        for num, results in enumerate(search(dork, tld="ru", lang="ru", num=int(col), start=0, stop=None, pause=2.0,
                                             extra_params={"filter": "0"})):
            if num == int(col):
                break
            print(f'\r[~] Поиск: {num + 1}/{col}', end='')
            res_list.append(results)
        return res_list
    except urllib.error.HTTPError:
        return 'error'

Создаем условие, где проверяем, является ли номер итерации цикла совпадающим с количеством нужных нам результатов. Если да, прерываем цикл. После чего печатаем результаты. На самом деле может быть найдено гораздо меньше документов, чем запрошено вами. Добавляем результат на каждой итерации в список. И после окончания цикла возвращаем список из функции. Также добавим обработку исключения, которая может возникать, когда вас заблокирует Google. В этом случае мы будем возвращать «error».


Поиск документов в DuckDuckGo

Изначально создавать функцию поиска в других поисковых системах не планировалось. Но, когда я достиг лимита поиска в Google, а скрипт не был протестирован должным образом, мне пришлось это сделать. Конечно же, с поиском Google данный поиск не сравниться. Хотя бы потому, что зачастую документы, которые есть в его индексе, уже давно отсутствуют на сайте. В этом плане у Гугла все получше. Да и результатов тут гораздо меньше. Потому, в данном скрипте я не ограничиваю поиск количеством документов. А вытаскиваю все, до чего можно дотянуться.

Для начала создадим вспомогательную функцию. Скажу честно, данный код я «прихватизировал» без зазрения совести уже очень давно. Не особо силен в java, но тем не менее, понять, что делает скрипт можно без особого труда. Данная функция нужна будет для прокрутки страницы, чтобы прогрузить все результаты и нужную нам кнопку «Больше результатов». Создадим функцию scroll_to_bottom(driver). На вход она принимает драйвер браузера, который используется в данный момент. Затем создаются переменные со старым и новым значением позиции на странице. И в бесконечном цикле выполняется java-скрипт, который и выполняет прокрутку. Как только значение old_position станет None, цикл прекратит свою работу.

Python:
def scroll_to_bottom(driver):
    old_position = 0
    new_position = None

    while new_position != old_position:
        # Get old scroll position
        old_position = driver.execute_script(
            ("return (window.pageYOffset !== undefined) ?"
             " window.pageYOffset : (document.documentElement ||"
             " document.body.parentNode || document.body);"))
        # Sleep and Scroll
        time.sleep(1)
        driver.execute_script((
            "var scrollingElement = (document.scrollingElement ||"
            " document.body);scrollingElement.scrollTop ="
            " scrollingElement.scrollHeight;"))
        # Get new position
        new_position = driver.execute_script(
            ("return (window.pageYOffset !== undefined) ?"
             " window.pageYOffset : (document.documentElement ||"
             " document.body.parentNode || document.body);"))

А теперь создадим функцию, в которой будем выполнять поиск. Я назвал ее get_bro(addr, docs). На входе она получает адрес сайта где будет выполняться поиск и список форматов для поиска. Создадим переменную с объектом опций для браузера. И добавим в нее значение headless равное True, которое означает, что браузер будет запускаться в «безголовом» режиме, то есть, без отображения самого приложения. Затем определим переменную, которая нужна для того, чтобы добавить в нее путь к драйверу браузера.

Так как в разных ОС версия будет различаться наличием или отсутствием расширения. А потому, определяем версию ОС. Если это Linux, проверяем, есть ли вообще такой файл. Если нет, сообщаем об этом пользователю и посылаем на сайт для загрузки. Если есть, присваиваем переменной значение пути к драйверу. Так же поступаем и для Windows. С Mac-осью возможности поэкспериментировать не было. Увы. Чего нет, того нет. Ну и создаем объект брайзера со всеми нужными параметрами, где указываем, что нужно запускать браузер в «безголовом» режиме, лог работы браузера не сохранять - log_path=os.devnull и указываем путь к драйверу.

Python:
 options = Options()
    options.headless = True

    path_ex = ''

    if platform.system() == "Linux":
        if not Path(Path.cwd() / 'geckodriver' / 'geckodriver').exists():
            print('[-] Нет драйвера для запуска браузера. Ссылка для загрузки: '
                  'https://github.com/mozilla/geckodriver/releases')
            return
        else:
            path_ex = Path.cwd() / 'geckodriver' / 'geckodriver'
    elif platform.system() == "Windows":
        if not Path(Path.cwd() / 'geckodriver' / 'geckodriver.exe').exists():
            print('[-] Нет драйвера для запуска браузера. Ссылка для загрузки: '
                  'https://github.com/mozilla/geckodriver/releases')
            return
        else:
            path_ex = Path.cwd() / 'geckodriver' / 'geckodriver.exe'

    browser = webdriver.Firefox(options=options, service=Service(log_path=os.devnull, executable_path=str(path_ex)))

Двигаемся дальше. Создаем множество для сохранения в него найденных ссылок на документы. Множество нужно для того, чтобы не было повторов. Так как DuckDuckGo все же Google, хоть он и поддерживает поиск со специальными операторами, но вот различать жестко типы документов, вроде doc и docx не умеет. А потому ищет сразу и то, и то. Но искать нужно оба типа. Так как в некоторых случаях в одном типе находятся не все документы, а при поиске во втором они уже присутствуют. Ну и вот потому, множество. Чтобы исключить повторы. В данное множество мы будем сохранять ссылки при поиске всех типов сразу. Затем запускаем цикл по типу документов указанных пользователем. Для примера: pdf doc docx и т. д. Формируем ссылку для поиска и отправляем по ней браузер. После того, как браузер загрузит нужную страницу, запускаем функцию, которая была создана нами ранее, функцию прокрутки страницы.

Python:
    link_set = set()

    print('\n[+] Поиск в DuckDuckGo')
    for item in docs:
        print(f'   - Поиск документов: "{item}"')
        url = f'https://duckduckgo.com/?q=site%3A{addr}+filetype%3A{item}&t=h_&ia=web'
        browser.get(url)
        scroll_to_bottom(browser)

Теперь запускаем бесконечный цикл и определяем счетчик, который нужен для перехода по страницам. Хоть результаты поиска подгружаются при нажатии на кнопку, формально здесь это переход на следующую станицу, а потому, циферка в Xpath будет меняться. То есть, для начала прокручиваем страницу до конца еще раз, на всякий случай. Затем ищем по Xpath кнопку загружающую результаты поиска с нужным номером страницы. И кликаем по кнопке. После чего увеличиваем счетчик и повторяем операцию. Ну и заключим код в try — except. Он нужен для обработки исключения, когда кнопка не будет найдена. По этому исключению мы будем прерывать цикл.

Python:
        num = 1
        while True:
            scroll_to_bottom(browser)
            try:
                more = browser.find_element(By.XPATH, f'//*[@id="rld-{num}"]/a')
                more.click()
                num += 1
            except selenium.common.exceptions.NoSuchElementException:
                break

Когда цикл завершиться, это будет означать, что результаты поиска полностью прогружены. Забираем код страницы и загружаем в BeautifulSoup для поиска. Здесь ищем теги в которых содержаться статьи со ссылками на нужные документы. Затем пробегаемся циклом по найденным тегам и выковыриваем статьи в которых содержаться ссылки. Запускаем еще цикл в цикле, где проверяем, не начинается ли найденная ссылка на поисковик и если нет, добавляем ссылку в множество. Если да, пропускаем итерацию. После того, как цикл будет завершен, сообщаем пользователю, что поиск документов закончился и закрываем браузер.

Python:
        html = browser.page_source
        soup = BeautifulSoup(html, 'lxml')
        links = soup.find_all('div', class_='nrn-react-div')

        for link in links:
            art = link.find('article').find_all('a', class_='eVNpHGjtxRBq_gLOfGDr')

            for a_link in art:
                if str(a_link['href']).startswith("https://duckduckgo.com"):
                    continue

                link_set.add(a_link['href'])
    print('\n[+] Поиск документов завершен')

    browser.close()
    browser.quit()

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

Python:
    if len(link_set) > 0:
        print(f'\n[+] Найдено ссылок: {len(link_set)}')

        dir_p = Path.cwd() / addr
        dir_p.mkdir(exist_ok=True)

        doc_downloads(link_set, addr)
    else:
        print(f'\n[-] Документов не найдено')

Python:
# функция прокрутки страницы в Selenium
def scroll_to_bottom(driver):
    old_position = 0
    new_position = None

    while new_position != old_position:
        # Get old scroll position
        old_position = driver.execute_script(
            ("return (window.pageYOffset !== undefined) ?"
             " window.pageYOffset : (document.documentElement ||"
             " document.body.parentNode || document.body);"))
        # Sleep and Scroll
        time.sleep(1)
        driver.execute_script((
            "var scrollingElement = (document.scrollingElement ||"
            " document.body);scrollingElement.scrollTop ="
            " scrollingElement.scrollHeight;"))
        # Get new position
        new_position = driver.execute_script(
            ("return (window.pageYOffset !== undefined) ?"
             " window.pageYOffset : (document.documentElement ||"
             " document.body.parentNode || document.body);"))


# функция получения ссылок из DuckDuckGo
def get_bro(addr, docs):
    options = Options()
    options.headless = True

    path_ex = ''

    if platform.system() == "Linux":
        if not Path(Path.cwd() / 'geckodriver' / 'geckodriver').exists():
            print('[-] Нет драйвера для запуска браузера. Ссылка для загрузки: '
                  'https://github.com/mozilla/geckodriver/releases')
            return
        else:
            path_ex = Path.cwd() / 'geckodriver' / 'geckodriver'
    elif platform.system() == "Windows":
        if not Path(Path.cwd() / 'geckodriver' / 'geckodriver.exe').exists():
            print('[-] Нет драйвера для запуска браузера. Ссылка для загрузки: '
                  'https://github.com/mozilla/geckodriver/releases')
            return
        else:
            path_ex = Path.cwd() / 'geckodriver' / 'geckodriver.exe'

    browser = webdriver.Firefox(options=options, service=Service(log_path=os.devnull, executable_path=str(path_ex)))

    link_set = set()

    print('\n[+] Поиск в DuckDuckGo')
    for item in docs:
        print(f'   - Поиск документов: "{item}"')
        url = f'https://duckduckgo.com/?q=site%3A{addr}+filetype%3A{item}&t=h_&ia=web'
        browser.get(url)
        scroll_to_bottom(browser)

        num = 1
        while True:
            scroll_to_bottom(browser)
            try:
                more = browser.find_element(By.XPATH, f'//*[@id="rld-{num}"]/a')
                more.click()
                num += 1
            except selenium.common.exceptions.NoSuchElementException:
                break
        html = browser.page_source
        soup = BeautifulSoup(html, 'lxml')
        links = soup.find_all('div', class_='nrn-react-div')

        for link in links:
            art = link.find('article').find_all('a', class_='eVNpHGjtxRBq_gLOfGDr')

            for a_link in art:
                if str(a_link['href']).startswith("https://duckduckgo.com"):
                    continue

                link_set.add(a_link['href'])
    print('\n[+] Поиск документов завершен')

    browser.close()
    browser.quit()

    if len(link_set) > 0:
        print(f'\n[+] Найдено ссылок: {len(link_set)}')

        dir_p = Path.cwd() / addr
        dir_p.mkdir(exist_ok=True)

        doc_downloads(link_set, addr)
    else:
        print(f'\n[-] Документов не найдено')


Функция запуска поиска Google

Создадим вспомогательную функцию в которой будем запускать поиск Google и перебирать полученные от пользователя типы файлов. Назову ее goo_start(addr, docs, res_count). Здесь на вход она получает адрес сайта, на котором нужно производить поиск, список типов документов, которые нужно найти и количество документов для поиска. То есть, если было введено 20 документов, до будет предпринята попытка найти 20 документов каждого типа. Запускаем цикл по списку типов документов. После чего передаем в функцию нужные параметры: адрес сайта, количество документов и тип. Если функция поиска не возвращает «error» двигаемся дальше. Проверяем, не равна ли длина списка результатов 0. Если не равна, сообщаем пользователю, сколько документов найдено. Создаем папку для сохранения в нее документов и запускаем функцию загрузки. Здесь логика немного отличается от той, что используется в поиске DuckDuckGo. Если там мы сначала ищем все, а только потом загружаем, то здесь мы ищем определенный тип, если найден грузим документы. Это нужно еще и для того, чтобы обеспечить паузу между поисками. Чтобы Google не забанил вас за слишком частые запросы. Спим еще 10 секунд и только потом продолжаем цикл. Ну, а если вернулась от функции поиска ошибка, то возвращаем из данной функции также «error». А если прилетел пустой список без ссылок, сообщаем, что документов нужного типа не найдено.

Python:
def goo_start(addr, docs, res_count):
    print('[+] Поиск в Google')
    for item in docs:
        res = search_g(addr, res_count, item)
        if res != 'error':
            if len(res) > 0:
                print(f'\n   [+] Найдено документов {item}: {len(res)}')

                dir_p = Path.cwd() / addr
                dir_p.mkdir(exist_ok=True)

                doc_downloads(res, addr)
        time.sleep(10)
            else:
                print(f'\n[-] {item} не найдено')
        else:
            print('[-] Превышено кол-во запросов')
            return 'error'


Запуск функций поиска

Перед тем, как описывать другие функции, надо пойти логически до конца, и определить функцию, которая будет запускать созданные поиски. Пока без загрузки файлов. Создадим функцию main(). Тут на вход ничего не подается. Для начала запрашиваем у пользователя нужные параметры, такие как сайта для поиска, который лучше указывать без протокола. Тип документов через пробел. То есть, можно ввести pdf doc pptx odt и т. д. Затем разбиваем список документов по пробелам. Проверяем, не является ли длина его первого элемента больше 4. Ну, к примеру: ppptx. Это будет гарантированно означать, что вы указали что-то неправильно. Так как расширения редко бывают больше. Без точки, конечно. Запрашиваем количество результатов поиска. Оно нужно для поиска Google.

Здесь немного подстрахуемся и если пользователь ничего не укажет, по умолчанию передадим 20. Получаем время старта скрипта и если список документов больше или равен одному запускаем вспомогательную функцию которая запускает поиск Google, в которую передаем сайт поиска, список документов, количество результатов поиска. Проверяем, что возвращает функция. Если ничего, значит все в порядке и запускаем вспомогательную функцию извлечения метаданных, а затем функцию всего полученного «добра». Если же был возвращен «error» запускаем функцию поиска по DuckDuckGo. Ну и в конце сообщаем пользователю время работы скрипта.

Python:
def main():
    addr = input('Введите сайт для поиска: ')
    doc_type = input('Введите тип документов для поиска: ')
    docs = doc_type.split()
    if len(docs[0]) > 4:
        print('- Неверный тип документов или не поставлен пробел')
        return
    res_count = int(input('Введите количество результатов поиска: ') or '20')
    print(' ')
    start = time.monotonic()
    if len(docs) >= 1:
        goo = goo_start(addr, docs, res_count)
        if goo == "error":
            get_bro(addr, docs)
        file_search(addr)
        result_save(addr)
    elif len(docs) == 0:
        print('- Вы не ввели тип документов для поиска')
        return
    print(f'\n[+] Время работы: {time.monotonic() - start}')

На этом первую часть статьи нужно закончить, иначе она превратиться в очень длинную портянку. А потому, на этом все. Продолжение в следующей части.

Python:
# pip install python-docx
# pip install pymupdf
# pip install requests
# pip install fake-useragent
# pip install olefile
# pip install selenium
# pip install Pillow
# pip install bs4
# pip install lxml
# pip install urllib3
# pip install google

import os
import platform
import re
import threading
import time
import urllib
import zipfile
from pathlib import Path
from xml.etree import ElementTree as etree

import docx
import fitz
import olefile
import requests
import selenium
from PIL import Image, ExifTags
from bs4 import BeautifulSoup
from docx.shared import Pt
from fake_useragent import UserAgent
from googlesearch import search
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.firefox.service import Service

requests.packages.urllib3.disable_warnings()

author = set()
soft = set()
email = set()


# функция поиска ссылок в Google
def search_g(site: str, col: int, doc: str):
    dork = f"site:{site} filetype:{doc}"
    res_list = []
    try:
        for num, results in enumerate(search(dork, tld="ru", lang="ru", num=int(col), start=0, stop=None, pause=2.0,
                                             extra_params={"filter": "0"})):
            if num == int(col):
                break
            print(f'\r[~] Поиск: {num + 1}/{col}', end='')
            res_list.append(results)
        return res_list
    except urllib.error.HTTPError:
        return 'error'


# функция прокрутки страницы в Selenium
def scroll_to_bottom(driver):
    old_position = 0
    new_position = None

    while new_position != old_position:
        # Get old scroll position
        old_position = driver.execute_script(
            ("return (window.pageYOffset !== undefined) ?"
             " window.pageYOffset : (document.documentElement ||"
             " document.body.parentNode || document.body);"))
        # Sleep and Scroll
        time.sleep(1)
        driver.execute_script((
            "var scrollingElement = (document.scrollingElement ||"
            " document.body);scrollingElement.scrollTop ="
            " scrollingElement.scrollHeight;"))
        # Get new position
        new_position = driver.execute_script(
            ("return (window.pageYOffset !== undefined) ?"
             " window.pageYOffset : (document.documentElement ||"
             " document.body.parentNode || document.body);"))


# функция получения ссылок из DuckDuckGo
def get_bro(addr, docs):
    options = Options()
    options.headless = True

    path_ex = ''

    if platform.system() == "Linux":
        if not Path(Path.cwd() / 'geckodriver' / 'geckodriver').exists():
            print('[-] Нет драйвера для запуска браузера. Ссылка для загрузки: '
                  'https://github.com/mozilla/geckodriver/releases')
            return
        else:
            path_ex = Path.cwd() / 'geckodriver' / 'geckodriver'
    elif platform.system() == "Windows":
        if not Path(Path.cwd() / 'geckodriver' / 'geckodriver.exe').exists():
            print('[-] Нет драйвера для запуска браузера. Ссылка для загрузки: '
                  'https://github.com/mozilla/geckodriver/releases')
            return
        else:
            path_ex = Path.cwd() / 'geckodriver' / 'geckodriver.exe'

    browser = webdriver.Firefox(options=options, service=Service(log_path=os.devnull, executable_path=str(path_ex)))

    link_set = set()

    print('\n[+] Поиск в DuckDuckGo')
    for item in docs:
        print(f'   - Поиск документов: "{item}"')
        url = f'https://duckduckgo.com/?q=site%3A{addr}+filetype%3A{item}&t=h_&ia=web'
        browser.get(url)
        scroll_to_bottom(browser)

        num = 1
        while True:
            scroll_to_bottom(browser)
            try:
                more = browser.find_element(By.XPATH, f'//*[@id="rld-{num}"]/a')
                more.click()
                num += 1
            except selenium.common.exceptions.NoSuchElementException:
                break
        html = browser.page_source
        soup = BeautifulSoup(html, 'lxml')
        links = soup.find_all('div', class_='nrn-react-div')

        for link in links:
            art = link.find('article').find_all('a', class_='eVNpHGjtxRBq_gLOfGDr')

            for a_link in art:
                if str(a_link['href']).startswith("https://duckduckgo.com"):
                    continue

                link_set.add(a_link['href'])
    print('\n[+] Поиск документов завершен')

    browser.close()
    browser.quit()

    if len(link_set) > 0:
        print(f'\n[+] Найдено ссылок: {len(link_set)}')

        dir_p = Path.cwd() / addr
        dir_p.mkdir(exist_ok=True)

        doc_downloads(link_set, addr)
    else:
        print(f'\n[-] Документов не найдено')


# функция запуска поиска в Google
def goo_start(addr, docs, res_count):
    print('[+] Поиск в Google')
    for item in docs:
        res = search_g(addr, res_count, item)
        if res != 'error':
            if len(res) > 0:
                print(f'\n   [+] Найдено документов {item}: {len(res)}')

                dir_p = Path.cwd() / addr
                dir_p.mkdir(exist_ok=True)

                doc_downloads(res, addr)
                time.sleep(10)
            else:
                print(f'\n[-] {item} не найдено')
        else:
            print('[-] Превышено кол-во запросов')
            return 'error'


# функция ввода пользовательских данных
# передача данных в функции запуска поиска
# запуск функции поиска метаданных
# запуск функции сохранения метаданных
def main():
    addr = input('Введите сайт для поиска: ')
    doc_type = input('Введите тип документов для поиска: ')
    docs = doc_type.split()
    if len(docs[0]) > 4:
        print('- Неверный тип документов или не поставлен пробел')
        return
    res_count = int(input('Введите количество результатов поиска: ') or '20')
    print(' ')
    start = time.monotonic()
    if len(docs) >= 1:
        goo = goo_start(addr, docs, res_count)
        if goo == "error":
            get_bro(addr, docs)
        file_search(addr)
        result_save(addr)
    elif len(docs) == 0:
        print('- Вы не ввели тип документов для поиска')
        return
    print(f'\n[+] Время работы: {time.monotonic() - start}')


if __name__ == "__main__":
    main()

Спасибо за внимание. Надеюсь, что данная информация будет вам полезна
 
Последнее редактирование:
  • Нравится
Реакции: qu1k и Maksim_K
Мы в соцсетях: