程序的主要功能是从 Telegram 频道中抓取消息数据,并将其保存到 MongoDB 数据库中,同时通过 Redis 来存储和管理消息的偏移数据,用于跟踪消息的处理进度。它还包含将消息通过企业微信机器人发送警告通知的功能。

1. 具体功能总结:

1.1. MongoDB 数据库连接与更新

程序使用 pymongo 连接 MongoDB 数据库,提供了 update 方法来将抓取到的 Telegram 消息数据保存到 MongoDB 中,采用 upsert 模式(如果消息已存在则更新,否则插入)。

1.2. Redis 用于存储偏移信息

通过 Redis 来记录处理的 Telegram 消息的偏移量,确保从上次处理过的消息开始获取历史记录,避免重复处理消息。

1.3. Telegram 客户端配置与消息抓取

使用 telethon 的 TelegramClient 来连接 Telegram API,并通过异步的 iter_messages 方法抓取指定频道的消息。 通过 format_message 方法将消息格式化为需要的结构化数据(包括消息文本、发送者信息、群组信息等),并返回一个字典格式的数据。 抓取的消息按照消息 ID 或者日期进行排序,以获取指定的历史记录。

1.4. 消息保存与处理

消息经过格式化后,会通过 mongo_cli.update() 保存到 MongoDB 中。 同时,使用 redis_cli.set 将当前处理的消息 ID 存入 Redis,以便下次继续处理新的消息。

1.5. 通过企业微信机器人发送通知

robot_warning 函数用于通过企业微信机器人发送通知。在某些场景下,抓取到的消息数据会被发送到企业微信中,作为系统的警告通知功能。

1.6. 定时任务调度

使用 apscheduler 的 AsyncIOScheduler 设置一个每隔 5 分钟运行一次的任务,调用 load_history_to_save 函数来抓取新的消息并进行处理。 使用 next_run_time=datetime.datetime.now() 来立即启动第一个任务执行。

1.7. 异步加载 Telegram 历史消息

load_history_to_save 函数会遍历所有对话(群组或频道),通过异步的方式抓取历史消息并存储到 MongoDB 中,间隔 10 秒处理每个群组。 使用 iter_dialogs 方法获取所有对话,依次处理每个群组的历史消息。

2. 总结:

该程序主要用于从 Telegram 群组或频道中抓取历史消息,并通过 MongoDB 和 Redis 进行数据存储与管理,结合定时调度任务自动获取最新的消息。同时,提供了一个企业微信通知机制来发送消息抓取的结果。

  1import asyncio
  2import datetime
  3import json
  4
  5import pymongo
  6import requests
  7from apscheduler.schedulers.asyncio import AsyncIOScheduler
  8from redis import Redis
  9from telethon import TelegramClient
 10# https://my.telegram.org/apps
 11from telethon.utils import get_display_name
 12
 13# 数据库基本信息
 14db_configs = {
 15    'type': 'mongo',
 16    'host': '127.0.0.1',
 17    'port': '27017',
 18    "user": "",
 19    "password": "",
 20    'db_name': 'new_spider'
 21}
 22
 23
 24class Mongo():
 25    def __init__(self):
 26        self.db_name = db_configs.get("db_name")
 27        self.host = db_configs.get("host")
 28        self.port = db_configs.get("port")
 29        self.client = pymongo.MongoClient(f'mongodb://{self.host}:{self.port}', connect=False, maxPoolSize=10)
 30        self.username = db_configs.get("user")
 31        self.password = db_configs.get("passwd")
 32        if self.username and self.password:
 33            self.db = self.client[self.db_name].authenticate(self.username, self.password)
 34        self.db = self.client[self.db_name]
 35
 36    def update(self, item, col="tg_spider"):
 37        if not isinstance(item, list):
 38            item = [item]
 39
 40        for each_item in item:
 41            coll = self.db[col]
 42            coll.update_one({"message_id": each_item["message_id"]}, {'$set': each_item}, upsert=True)
 43
 44
 45redis_cli = Redis.from_url("redis://@localhost:6379")
 46last_offset_date = datetime.datetime.strptime("2022-10-01", "%Y-%m-%d")  # 设置开始时间
 47api_id = xxxx
 48
 49api_hash = xxxxxx
 50
 51channel_dict = {}
 52
 53client = TelegramClient('ztelegrm', api_id, api_hash, connection_retries=15, retry_delay=3)
 54robot_url = xxxxx
 55
 56mongo_cli = Mongo()
 57
 58
 59def robot_warning(text, url):
 60    """
 61    企业微信机器人
 62    text: 文本
 63    url: 链接
 64
 65    """
 66    if isinstance(text, dict) or isinstance(text, list):
 67        text = json.dumps(text, ensure_ascii=False)
 68    headers = {
 69        "Content-Type": "application/json"
 70    }
 71    data = {
 72        "msgtype": "text",
 73        "text": {
 74            "content": text,
 75        }
 76    }
 77    return requests.post(url, headers=headers, data=json.dumps(data))
 78
 79
 80async def format_message(message, chat_id):
 81    if not message.text or message.media:
 82        return None
 83
 84    chat = await message.get_chat()
 85
 86    chat_display_name = get_display_name(chat)
 87    sender_user = await message.get_sender()
 88
 89    content = message.text
 90    talker = chat_id
 91    tg_id = getattr(sender_user, 'username', '')
 92    create_time = message.date.strftime("%s")
 93    group_name = chat_display_name
 94    first_name = getattr(sender_user, 'first_name', '')
 95    last_name = getattr(sender_user, 'last_name', '')
 96    if tg_id is None:
 97        tg_id = "未知"
 98    if last_name is None and first_name is None:
 99        user_name = "未知"
100    else:
101        user_name = f"{first_name}_{last_name}"
102    send_data = f"{content}|$#$#|{talker}|$#$#|{tg_id}|$#$#|{create_time}|$#$#|{group_name}|$#$#|{user_name}"
103    redis_cli.set(f"tg_cache_id_{chat_id}", message.id)
104    # return send_data
105
106    doc_data = {
107        "timestamp": message.date.strftime("%s"),
108        "sender": {
109            "username": getattr(sender_user, 'username', ''),
110            "firstName": getattr(sender_user, 'first_name', ''),
111            "lastName": getattr(sender_user, 'last_name', '')
112        },
113        "channel": chat_display_name,
114        "channel_id": chat_id,
115        "text": message.text,
116        "message_id": message.id
117    }
118    return doc_data
119
120
121async def get_channel_dict():
122    async for d in client.iter_dialogs():
123        channelId = d.entity.id
124        channelName = d.name
125        # channel_dict[channelId] = channelName
126        yield channelId, channelName
127
128
129async def load_history_to_save():
130    channel_dict = get_channel_dict()
131    async for channel_info in channel_dict:
132        channel_id = channel_info[0]
133        min_id = redis_cli.get(f"tg_cache_id_{channel_id}")
134        if min_id is None:
135            min_id = 0
136        print(channel_id, channel_info[1])
137        entity = await client.get_entity(channel_id)
138        await asyncio.sleep(10)
139        message_list = []
140        # 每次查50条,limit=50
141        if min_id == 0:
142            # 这个地方含义是获取2022-10-01这天的数据,从旧数据到新数据的顺序,这样message id逐渐增大。
143            async for message in client.iter_messages(entity, reverse=True, offset_date=last_offset_date, limit=50):
144                doc_data = await format_message(message, channel_id)
145                if doc_data:
146                    message_list.append(doc_data)
147        else:
148            # 根据message id获取数据,id逐渐增大。
149            async for message in client.iter_messages(entity, reverse=True, min_id=int(min_id),
150                                                      limit=50):
151                doc_data = await format_message(message, channel_id)
152                if doc_data:
153                    message_list.append(doc_data)
154        # robot_warning(message_list, robot_url)
155        mongo_cli.update(message_list)
156
157        print("发送完成")
158
159
160def start():
161    with client:
162        scheduler = AsyncIOScheduler()
163        scheduler.add_job(load_history_to_save, 'interval', minutes=5, max_instances=1,
164                          next_run_time=datetime.datetime.now())
165        scheduler.start()
166        client.loop.run_forever()
167
168
169if __name__ == '__main__':
170    start()