[PyACC 9] Python - Kiến thức nâng cao - P1.2

Đây là bài cuối cùng trong series "Python cơ bản". Ở bài này chúng ta sẽ cùng nhau tìm hiểu cách để tạo ra một con bot telegram, sau đó bổ sung thêm mã nguồn cho đoạn code ở phần trước để con bot telegram có thể gửi tin mới về bitcoin cho chúng ta.

Tạo Telegram Bot

Để tạo Telegram bot, bạn sẽ phải chat với BotFather và dùng lệnh /newbot để yêu cầu nó tạo cho bạn một con bot.

Bây giờ, bạn truy cập đường link dưới đây:

https://web.telegram.org/k/#@BotFather
Telegram Web
Telegram is a cloud-based mobile and desktop messaging app with a focus on security and speed.

Sau đó, chat như hình bên dưới:

Trong đó:

  • tempify là tên mình đặt cho bot, bạn nên đặt tên khác để không bị trùng.
  • tempify_bot tương tự là username của bot, bạn lấy tên đã đặt trước đó, bổ sung thêm _bot là xong. Ví dụ tên bot là mina thì username sẽ là mina_bot.

Cuối cùng, nếu tất cả hợp lệ, API Key sẽ hiện ra như hình bên dưới.

Lưu ý:
API Key trên ảnh chỉ để minh họa. Trong thực tế, mỗi con bot sẽ có API Key riêng và không trùng lặp. Do đó bạn hãy copy API Key của bạn để dùng cho đoạn code của riêng cá nhân nhé.

Sử dụng Telegram Bot API Key để gửi tin nhắn

Để con bot của bạn gửi được tin nhắn chúng ta cần ít nhất 2 thứ:

  • Đích hay người mà bot sẽ phải gửi tin nhắn đến: Trong trường hợp này là chính bạn.
  • Nội dung của tin nhắn: Chính là những bài viết về bitcoin mà chúng ta đã cùng nhau thu thập trong buổi trước.

Ok, cụ thể sẽ như thế nào?

Đích hay người mà bot sẽ phải gửi tin nhắn đến

Trong trường hợp này là chính bạn. Nhưng bot nó sẽ không biết bạn là ai, nó ko quan tâm hình thù của bạn như thế nào. Tất cả những gì nó quan tâm là ID của bạn.

Để lấy ID của cá nhân bạn, bạn phải chat với một con bot khác, nó tên là @userinfobot.

https://web.telegram.org/k/#@userinfobot
Telegram Web
Telegram is a cloud-based mobile and desktop messaging app with a focus on security and speed.

Bạn làm theo hướng dẫn như trên hình minh họa dưới đây. Phần mình che đỏ chính là một dãy số và nó là ID, bạn chỉ cần lấy dãy số của riêng bạn là được.

Nội dung tin nhắn

Phần này chúng ta đã khá rõ từ bài trước.

Ở bài trước chúng ta đã thu thập danh sách bài viết về và lưu nó trong file .csv. Ở bài này, thay vì lưu vào file, chúng ta sẽ gửi trực tiếp những bài viết này đến telegram của cá nhân.

Video hướng dẫn cụ thể:

Full source code:

import time

import requests
import telegram
from lxml import html
from cacheout import Cache


def fetch_html_response(url):
    headers = {
        "authority": "cointelegraph.com",
        "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
        "accept-language": "en-US,en;q=0.9,vi;q=0.8",
        "cache-control": "no-cache",
        "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
    }

    response = requests.request("GET", url, headers=headers)

    return response


def extract_article_image(response):
    tree = html.fromstring(response.content.decode(response.encoding))
    images = tree.xpath('//meta[@property="og:image"]//@content')
    if images:
        return images[0]
    else:
        return None


def extract_articles(response):
    tree = html.fromstring(response.content.decode(response.encoding))

    article_elements = tree.xpath('//li[@class="posts-listing__item"]//article[@class="post-card-inline"]')

    articles = []
    for element in article_elements:
        title = None
        url = None
        description = None
        time = None
        author = None
        view_count = None

        titles = element.xpath('.//a[@class="post-card-inline__title-link"]//span//text()')
        if titles:
            title = ''.join(titles)

        hrefs = element.xpath('.//a[@class="post-card-inline__title-link"]//@href')
        if hrefs:
            href = ''.join(hrefs)
            url = f'https://cointelegraph.com/{href}'

        descriptions = element.xpath('.//p[@class="post-card-inline__text"]//text()')
        if descriptions:
            description = ''.join(descriptions)

        time_agos = element.xpath('.//div[@class="post-card-inline__meta"]//time//text()')
        time_ago = ''.join(time_agos)

        times = element.xpath('.//div[@class="post-card-inline__meta"]//time//@datetime')
        time = ''.join(times)
        time = time if 'ago' not in time_ago else f'{time} {time_ago}'

        authors = element.xpath('.//div[@class="post-card-inline__meta"]//p[@class="post-card-inline__author"]//a//text()')
        author = ''.join(authors).strip()

        view_counts = element.xpath('.//div[@class ="post-card-inline__stats"]//span//text()')
        if view_counts:
            view_count = ''.join(view_counts)
            if view_count:
                view_count = view_count.replace("&nbsp", "")
                view_count = int(view_count)

        article = {
            'title': title.strip(),
            'url': url.strip(),
            'description': description.strip(),
            'time': time.strip(),
            'author': author.strip(),
            'view_count': view_count,
        }

        articles.append(article)
    return articles


if __name__ == '__main__':
    api_key = 'YOUR_API_KEY'
    user_id = 'YOUR_ID'
    cache = Cache(maxsize=1000, ttl=86400)

    while True:
        html_response = fetch_html_response(url="https://cointelegraph.com/tags/bitcoin")
        articles = extract_articles(html_response)
        if not articles:
            print(f'Cannot found any articles!')
        else:
            print(f'Fetched:: {len(articles)} articles')
            bot = telegram.Bot(token=api_key)
            for article in articles:
                if 'ago' not in article['time']:
                    print(f'Too old article:: {article["url"]}')
                    continue

                cached = cache.get(article['url'])
                if cached:
                    print(f'Cached article:: {article["url"]}')
                    continue

                text = f'{article["title"]}\n' \
                       f'{article["description"]}\n' \
                       f'{article["author"]} - {article["time"]} - {article["view_count"]} views\n' \
                       f'-------------------------------------------'
                image_response = fetch_html_response(article['url'])
                article['image'] = extract_article_image(image_response)

                if article['image']:
                    bot.send_photo(chat_id=user_id,
                                   photo=article['image'],
                                   caption=article['url']
                                   )
                cache.set(article['url'], 1)
                bot.send_message(chat_id=user_id, text=text)
                print(f'Sent article:: {article["url"]}')

        time.sleep(5 * 60)