Перейти к публикации
Форум ботоводов
admin

Как воровать статьи с Форклога

Рекомендованные сообщения

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

Итак, в качестве жертвы возьмем Форклог- мне он нравится, я подписан на два его канала в телеграмме, думаю будет не лишним, если его новости будут дублироваться еще и тут. Удобно, знаете ли ) Ну, и кроме того, он не против - специально заскриню на всякий случай:

image.png.950353a945f9eeb1ebfa27c84e3722a6.png

Активную ссылку оставим - это вообще должно касаться любого источника данных в интернете (включая и материалы этого форума)

Итак, давайте начнем.

Первым делом думаем, как бы заполучить ссылки на последние посты - есть много вариантов, но самый простой, это проверить типичные SEO маршруты - sitemap.xml и rss.

Подставляем в URL эти волшебные слова и voila!

image.png.83f7f9702ed59c50f9081b88668e4721.png

image.png.497721688b0cdc30e31851e4c34ecc14.png

(c https://forklog.com/rss/ произошел редирект на /feed/)

Честно говоря, даже обидно, что не надо ничего толком парсить.

В sitemap.xml хранятся ссылки на вообще всё, что есть на сайте - включая посты за три года. Это тоже своего рода сокровище, конечно, и даже ультра интересная вещь для анализа, но для текущих задач такие архивы не нужны - их долго скачивать, их нужно распаковывать и т.п. Как-нибудь в другой раз. Сегодня возьмем RSS

import requests
from xml.etree import ElementTree as etree

res = requests.get("https://forklog.com/feed/")

root = etree.fromstring(res.text)
item = root.findall('channel/item')

for entry in item:   
    link = entry.findtext('link')
    title = entry.findtext('title')                     
    print(link)
    print(title)
    print('**********************')

Вот такой простой скрипт выведет все последние заголовки и ссылки на них на форклоге почти любом сайте на движке WordPress (если на другом сайте не подошло feed подставляйте rss, процентов 80, что сработает)

Результат примерно такой:

image.png.19a87dc1aa3f2f941493a98fac27c629.png

Теперь задача в том, что бы понимать, что уже своровано, а что еще нет. Первая мысль, которая может придти в голову, это куда-то записывать ссылку или title, и сравнивать с последней записью (по времени, в списке она выводится первой). 

Забудьте, если статью удалят или переименуют, всё пойдет не так. К счастью, хотя ForkLog и прячет ссылки за ЧПУ, все равно можно узнать ID каждой новости - в том же rss есть т.н. пермалинки - постоянные адреса страниц, которые ни от чего не зависят, вот такие:

<guid isPermaLink="false">https://forklog.com/?p=61294</guid>

Вот их и заюзаем.

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

import os
import re
import requests
from xml.etree import ElementTree as etree

# Сюда будем писать последнее полученное ID 
last_id_file = "./forklog_last_id"
last_id = 0

# Если файла не существует, то создадим и запишем туда 0
if not os.path.exists(last_id_file):
    with open(last_id_file, "w") as out:
        out.write(str(last_id))
else:
    with open(last_id_file, 'r+') as inp:
        last_id = int(inp.read())

# Получить содержимое feed-ленты
res = requests.get("https://forklog.com/feed/", verify=False)

# Разобрать полученный xml на элементы
root = etree.fromstring(res.text)
item = root.findall('channel/item')

new_last_id = last_id
for pos, entry in enumerate(item):
    try:
        # Вычленить ID новости
        permalink = entry.findtext('guid')
        post_id = int(re.sub("\D", "", permalink))
        
        link = entry.findtext('link')
        title = entry.findtext('title')

        # Текущая новость уже была, нет смысла смотреть дальше
        if post_id <= last_id:
            print("Наткнулся на старую новость, выход\n", title, link )
            break
        if pos == 0:
            # т.к. мы уйдем ниже по списку новостей, запомним самую последнюю
            new_last_id = post_id
        print(link)
        print(post_id, permalink)   
        print(title)
        print('**********************')
        
    except Exception as e:
        print('Что то пошло не так', e)

# Обновим записанное в файле значение, что бы потом не трогать старые новости
if new_last_id != last_id:
     with open(last_id_file, "w") as out:
        out.write(str(new_last_id))

Всё, получили данные, теперь можно их куда-нибудь вставлять. А скрипт можно запускать по планировщику, каждые 10 минут, например.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Итак, теперь мы можем получать ссылки на самые последние статьи. Но нам-то нужен полный текст новости со всем содержимым - его не будет в RSS. Давайте парсить страницы. Логика усложнится - теперь мы для каждой новой новости (пфф) будем получать текст её страницы, и разбирать её на составляющие. Можно делать это регулярными выражениями или вообще голой логикой в стиле программирования на C, но мы пойдем ленивым путем и поставим модуль beautifulsoup4

pip install beautifulsoup4

И давайте испытывать её в деле.

Откроем исходный код страницы любой новости с сайта и изучим. Найдем интересующие места:

image.thumb.png.c27d343595ea4751ecc344222dac5f01.png

Нас интересует всё, что есть в тегах <p> в теге <section id="article_content">. Давайте просто это заберем

import os
import re
import time
from bs4 import BeautifulSoup
import requests
from xml.etree import ElementTree as etree

# Сюда будем писать последнее полученное ID 
last_id_file = "./forklog_last_id"
last_id = 0

# Если файла не существует, то создадим и запишем туда 0
if not os.path.exists(last_id_file):
    with open(last_id_file, "w") as out:
        out.write(str(last_id))
else:
    with open(last_id_file, 'r+') as inp:
        last_id = int(inp.read())

# Получить содержимое feed-ленты
res = requests.get("https://forklog.com/feed/", verify=False)

# Разобрать полученный xml на элементы
root = etree.fromstring(res.text)
item = root.findall('channel/item')

new_last_id = last_id
for pos, entry in enumerate(item):
    try:
        # Вычленить ID новости
        permalink = entry.findtext('guid')
        post_id = int(re.sub("\D", "", permalink))
        
        link = entry.findtext('link')
        title = entry.findtext('title')


        

        # Текущая новость уже была, нет смысла смотреть дальше
        if post_id <= last_id:
            print("Наткнулся на старую новость, выход\n", title, link )
            break
        if pos == 0:
            # т.к. мы уйдем ниже по списку новостей, запомним самую последнюю
            new_last_id = post_id

        news_res = requests.get(link, verify=False)
        soup = BeautifulSoup(news_res.text, 'html.parser')
        
        article_text = ''
        article = soup.find("section", {"id":"article_content"}).findAll('p')
        
        for element in article:
            article_text += '\n' + ''.join(element.findAll(text = True))
        
        print(link)
        print(post_id, permalink)   
        print(title)
        
        print('-'*80)
        print(article_text)
        print('-'*80)

        print('**********************')
        # Что бы не мешать частыми запросами и что бы не забанили
        time.sleep(2)
    except Exception as e:
        print('Что то пошло не так', e)

# Обновим записанное в файле значение, что бы потом не трогать старые новости
if new_last_id != last_id:
     with open(last_id_file, "w") as out:
        out.write(str(new_last_id))

Готово, теперь бот может забирать тексты статей. Изображения будут тянуться напрямую с сайта Форклога. Конечно, это не очень хорошо, и надо бы их перекачивать себе, но пусть пока что будет так :)

Результат выглядит вот так (статья сменилась, пока тестировал):

image.png.904158d3d1cd79d748868da3253d9bf3.png

 

 

  • Like 1

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Ну, а теперь последний шаг - давайте постить всё найденное на форум (IPB в данном случае). В админке я создал API ключ для своего скрипта (но вам не покажу), и познал сокрушительный удар - оказывается, Invision прячется за CloudFront, а те режут Post запросы, поля которых содержат больше 2кб.... Ну что ж делать, не выбрасывать же скрипт.. Теперь будут вороваться те новости, в которых меньше 2000 байт :)

import os
import re
import time
from bs4 import BeautifulSoup
import requests
from xml.etree import ElementTree as etree

forum_id = 13
api_key = '....'
author_id=3
max_text_size=2000


# Сюда будем писать последнее полученное ID 
last_id_file = "./forklog_last_id"
last_id = 0

# Если файла не существует, то создадим и запишем туда 0
if not os.path.exists(last_id_file):
    with open(last_id_file, "w") as out:
        out.write(str(last_id))
else:
    with open(last_id_file, 'r+') as inp:
        last_id = int(inp.read())

# Получить содержимое feed-ленты
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36'}

res = requests.get("https://forklog.com/feed/", headers=headers, verify=False)

# Разобрать полученный xml на элементы
root = etree.fromstring(res.text)
item = root.findall('channel/item')



new_last_id = last_id
for pos, entry in enumerate(item):
    try:
        # Вычленить ID новости
        permalink = entry.findtext('guid')
        post_id = int(re.sub("\D", "", permalink))
        
        link = entry.findtext('link')
        title = entry.findtext('title')


        

        # Текущая новость уже была, нет смысла смотреть дальше
        if post_id <= last_id:
            print("Наткнулся на старую новость, выход\n", title, link )
            break
        if pos == 0:
            # т.к. мы уйдем ниже по списку новостей, запомним самую последнюю
            new_last_id = post_id

        news_res = requests.get(link, verify=False)
        soup = BeautifulSoup(news_res.text, 'html.parser')
        
        article_text = ''
        article = soup.find("section", {"id":"article_content"}).findAll('p')
        
        for element in article:
            #article_text += '\n' + ''.join(element.findAll(text = True))
            article_text += str(element)
        
        print(link)
        print(post_id, permalink)   
        print(title)
        
        print('-'*80)
        print(len(article_text), article_text)
        print('-'*80)
        if article_text:
            article_text += '<p><a href="'+link+'" target="_blank">Ссылка на оригинал статьи</a></p>'
            
        if len(article_text) < max_text_size:
            
            
            res = requests.post(
                'https://forum.bablofil.ru/api/forums/topics?key=' + api_key,
                params={
                    'forum':forum_id,
                    'author':author_id,
                    'title':title,
                    'post':article_text
                },
                headers=headers,
                verify=False
            )
            print(res.text)
            
        print('**********************')
        # Что бы не мешать частыми запросами и что бы не забанили
        time.sleep(13)
    except Exception as e:
        print('Что то пошло не так', e)

# Обновим записанное в файле значение, что бы потом не трогать старые новости
if new_last_id != last_id:
     with open(last_id_file, "w") as out:
        out.write(str(new_last_id))

А результат вы можете видеть в разделе новостей на этом форуме. 

За сим всё, оставил скрипт работать на сервере. Надеюсь, будет кому-то полезным

 

  • Like 2

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Нет ни одного ресурса где так подробно по кусочкам разбирают это дело , главное понятно даже новичку в этом деле , доходчиво все ) Посмотрел пару курсов по змейке , именно то что я хотел , просмотрел много уроков на ютубе но там те кто объясняют делают это лениво и с таким голосом что сразу хочется спать ... Респект автору ! спасибо за труд ! ждем продолжения 

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Этот способ дает ошибки на других сайтах - особенно повидемому из-за ошибки в символах неразрешенных в XLM.

Как прочитать новости например отсюда: https://www.six-group.com/exchanges/news/overview_de.html
Еще как прочитать новости с помощью запроса

conn = http.client.HTTPConnection(…..)
conn.request(http_method,......, headers)
response = conn.getresponse().read()

raw строку получаю, а как расшифровать не могу - ошибка вылетает.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
27.11.2018 в 13:48, Nikolaev Nikolay сказал:

Этот способ дает ошибки на других сайтах - особенно повидемому из-за ошибки в символах неразрешенных в XLM.

Как прочитать новости например отсюда: https://www.six-group.com/exchanges/news/overview_de.html
Еще как прочитать новости с помощью запроса

conn = http.client.HTTPConnection(…..)
conn.request(http_method,......, headers)
response = conn.getresponse().read()

raw строку получаю, а как расшифровать не могу - ошибка вылетает.

Этот сайт не на движке WordPress, а Adobe Experience Manager, это относительно редкий движок, для них нужно отдельно скрипт писать, сначала распарсить на ссылки меню с этой страницы, потом перейти по каждой ссылке и парсить ссылки на тех страницах, пока не встретятся страницы с контентом. В общем, это разовая большая задача.

Насчет второго вопроса - 

res_text = response.decode('utf-8')
print(res_text)

Ну а ошибки можно перехватывать через try/except, игнорировать или обрабатывать, смотря что за ошибки

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Создайте аккаунт или войдите в него для комментирования

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

Создать аккаунт

Зарегистрируйтесь для получения аккаунта. Это просто!

Зарегистрировать аккаунт

Войти

Уже зарегистрированы? Войдите здесь.

Войти сейчас

×