Скрэппинг лидов в Telegram: полное техническое руководство по извлечению квалифицированных лидов
Полное техническое руководство по скрэппингу лидов в Telegram: обнаружение групп, извлечение участников, майнинг комментариев, обогащение профилей, детекция намерений и архитектура пайплайна данных. С рабочими примерами кода для каждого компонента.
Скрэппинг лидов в Telegram: полное техническое руководство
Введение
Скрэппинг лидов в Telegram принципиально отличается от сканирования LinkedIn или email. Открытый API платформы, структурированное членство в группах и доступная история сообщений делают её самой сканируемой крупной платформой для B2B генерации лидов.
Это руководство охватывает все технические аспекты: обнаружение групп, извлечение участников, майнинг комментариев, обогащение профилей, детекция намерений и построение полного пайплайна данных.
Часть 1: Настройка инфраструктуры
Конфигурация доступа к API
Telegram предоставляет два уровня API:
Bot API - Ограниченный, но стабильный. Боты могут обращаться только к группам, в которые они добавлены. User API (MTProto) - Полный доступ через личные аккаунты. Это основной инструмент для скрэппинга лидов.Настройка Telethon
pip install telethon
from telethon import TelegramClient
api_id = 12345678
api_hash = ''your_api_hash_here''
phone = ''+1234567890''
client = TelegramClient(f''sessions/{phone}'', api_id, api_hash)
async def initialize():
await client.start(phone)
me = await client.get_me()
print(f''Залогинен как: {me.first_name}'')
with client:
client.loop.run_until_complete(initialize())
Настройка нескольких аккаунтов
class AccountPool:
def __init__(self, config_path=''accounts.json''):
with open(config_path) as f:
self.accounts = json.load(f)
self.clients = {}
self.usage = {}
def get_best_account(self):
min_usage = float(''inf'')
best_index = 0
for i, account in enumerate(self.accounts):
usage = self.usage.get(i, 0)
if usage < min_usage and usage < account.get(''daily_limit'', 200):
min_usage = usage
best_index = i
return best_index
Часть 2: Обнаружение групп
Автоматическое обнаружение групп
class GroupDiscovery:
def __init__(self, client):
self.client = client
async def evaluate_group(self, group_username):
entity = await self.client.get_entity(group_username)
full_chat = await self.client(GetFullChatRequest(entity))
participants_count = full_chat.full_chat.participants_count or 0
messages = []
async for msg in self.client.iter_messages(entity, limit=100):
messages.append(msg)
if not messages:
return None
oldest_msg = messages[-1].date
newest_msg = messages[0].date
time_span_days = max((newest_msg - oldest_msg).days, 1)
messages_per_day = len(messages) / time_span_days
score = 0
if 5000 <= participants_count <= 50000:
score += 30
elif 1000 <= participants_count <= 5000:
score += 20
score += min(messages_per_day / 10, 30)
return {
''username'': group_username,
''participants_count'': participants_count,
''messages_per_day'': messages_per_day,
''overall_score'': score
}
Часть 3: Извлечение участников
Базовое сканирование участников
async def scrape_group_members(group_username, limit=1000):
client = TelegramClient(''session'', api_id, api_hash)
await client.start()
members = []
entity = await client.get_entity(group_username)
offset = 0
batch_size = 100
while offset < limit:
result = await client(GetParticipantsRequest(
channel=entity,
filter=ChannelParticipantsSearch(''''),
offset=offset,
limit=batch_size,
hash=0
))
if not result.users:
break
for user in result.users:
if not user.bot and not user.deleted:
members.append({
''user_id'': user.id,
''username'': user.username,
''first_name'': user.first_name,
''last_name'': user.last_name,
''phone'': user.phone,
''is_premium'': getattr(user, ''premium'', False),
''group'': group_username
})
offset += len(result.users)
await asyncio.sleep(2)
await client.disconnect()
return members
Часть 4: Извлечение лидов по комментариям
Детекция намерений
class CommentExtractor:
def __init__(self, client):
self.client = client
self.intent_keywords = {
''high'': [''recommend'', ''suggest'', ''looking for'', ''need help'',
''рекомендуете'', ''посоветуйте'', ''ищу'', ''нужна помощь''],
''medium'': [''how to'', ''what is'', ''best way'',
''как сделать'', ''что такое'', ''лучший способ''],
''low'': [''interesting'', ''tell me more'',
''интересно'', ''расскажи больше'']
}
async def extract_comments_with_intent(self, group_username, hours_back=24):
entity = await self.client.get_entity(group_username)
cutoff_time = datetime.now() - timedelta(hours=hours_back)
leads = []
async for message in self.client.iter_messages(entity, offset_date=cutoff_time):
if not message.text or message.reply_to is None:
continue
text = message.text.lower()
intent_level = self.detect_intent(text)
if not intent_level:
continue
sender = await message.get_sender()
if sender.bot or sender.deleted:
continue
leads.append({
''user_id'': sender.id,
''username'': sender.username,
''first_name'': sender.first_name,
''bio'': sender.about or '''',
''comment'': message.text[:500],
''intent_level'': intent_level,
''timestamp'': message.date.isoformat(),
''group'': group_username
})
return leads
def detect_intent(self, text):
for level, keywords in self.intent_keywords.items():
for keyword in keywords:
if keyword in text:
return level
return None
Часть 5: Обогащение профилей
async def enrich_profile(user, client):
profile = {
''telegram_id'': user.id,
''username'': user.username,
''first_name'': user.first_name,
''last_name'': user.last_name,
''phone'': user.phone,
''bio'': user.about or '''',
''is_premium'': getattr(user, ''premium'', False),
''has_photo'': bool(user.photo),
}
bio_text = profile[''bio''].lower()
company_patterns = [r''at (\w+)'', r''@ (\w+)'', r''working at (\w+)'']
for pattern in company_patterns:
match = re.search(pattern, bio_text)
if match:
profile[''company''] = match.group(1)
break
role_keywords = {
''cto'': [''cto'', ''technical director'', ''технический директор''],
''ceo'': [''ceo'', ''founder'', ''основатель''],
''cmo'': [''cmo'', ''marketing director'', ''директор по маркетингу''],
''developer'': [''developer'', ''engineer'', ''разработчик'']
}
for role, keywords in role_keywords.items():
if any(kw in bio_text for kw in keywords):
profile[''role''] = role
break
return profile
Часть 6: Оценка намерений
class IntentScorer:
def __init__(self):
self.weights = {
''comment_intent'': 0.25,
''profile_match'': 0.20,
''activity_level'': 0.15,
''recency'': 0.15,
''question_asked'': 0.15,
''company_match'': 0.10
}
def calculate_intent_score(self, lead_data):
scores = {}
if lead_data.get(''intent_level'') == ''high'':
scores[''comment_intent''] = 90
elif lead_data.get(''intent_level'') == ''medium'':
scores[''comment_intent''] = 60
else:
scores[''comment_intent''] = 10
scores[''profile_match''] = self.calculate_profile_match(lead_data)
scores[''activity_level''] = min(lead_data.get(''message_count'', 0) * 10, 100)
scores[''question_asked''] = 80 if lead_data.get(''question'') else 20
scores[''company_match''] = 70 if lead_data.get(''company_match'') else 30
return sum(scores[k] * self.weights[k] for k in self.weights)
Часть 7: Архитектура пайплайна данных
[Обнаружение групп] -> [Оценка групп] -> [Извлечение участников] -> [Обогащение профилей]
| | | |
API TGStat NLP-анализ API Telethon Внешние API
| | | |
[Майнинг комментариев] -> [Детекция намерений] -> [Поведенческий анализ] -> [Оценка намерений]
| | | |
API сообщений Ключевое слово Детекция паттернов Взвешенная оценка
| | | |
[Хранение данных] -> [Квалификация лидов] -> [CRM интеграция] -> [Готовность к outreach]
| | | |
PostgreSQL Порог оценки Webhook/API Готовность кампании
Схема базы данных
CREATE TABLE leads (
id SERIAL PRIMARY KEY,
telegram_id BIGINT UNIQUE,
username VARCHAR(255),
first_name VARCHAR(255),
last_name VARCHAR(255),
phone VARCHAR(50),
bio TEXT,
is_premium BOOLEAN DEFAULT FALSE,
has_photo BOOLEAN DEFAULT FALSE,
scraped_at TIMESTAMP DEFAULT NOW(),
intent_score FLOAT DEFAULT 0,
status VARCHAR(50) DEFAULT ''new'',
metadata JSONB DEFAULT ''{}''
);
CREATE TABLE lead_sources (
id SERIAL PRIMARY KEY,
lead_id INTEGER REFERENCES leads(id),
source_type VARCHAR(50),
source_group VARCHAR(255),
source_context TEXT,
discovered_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_leads_score ON leads(intent_score DESC);
CREATE INDEX idx_leads_status ON leads(status);
Часть 8: Масштабирование и оптимизация
Параллельная обработка
async def parallel_scrape(groups, max_concurrent=5):
semaphore = asyncio.Semaphore(max_concurrent)
async def scrape_with_semaphore(group):
async with semaphore:
return await scrape_group(group)
tasks = [scrape_with_semaphore(g) for g in groups]
results = await asyncio.gather(*tasks, return_exceptions=True)
successful = [r for r in results if not isinstance(r, Exception)]
return successful
Кеширование
class CacheManager:
def __init__(self, redis_url=''redis://localhost:6379''):
self.redis = redis.from_url(redis_url)
self.default_ttl = 3600 * 24
def cached(self, ttl=None):
def decorator(func):
@wraps(func)
async def wrapper(args, *kwargs):
cache_key = f''{func.__name__}:{hash(str(args) + str(kwargs))}''
cached = self.redis.get(cache_key)
if cached:
return json.loads(cached)
result = await func(args, *kwargs)
self.redis.setex(cache_key, ttl or self.default_ttl, json.dumps(result, default=str))
return result
return wrapper
return decorator
Часть 9: Соответствие и лучшие практики
- Сканируйте только публичные группы
- Не храните номера телефонов без явного согласия
- Предоставляйте механизм отказа во всех outreach
- Уважайте лимиты速率
- Документируйте действия по обработке данных
Заключение
Скрэппинг лидов в Telegram обеспечивает фундамент для автоматической генерации лидов. Описанная здесь техническая инфраструктура обеспечивает непрерывное обнаружение, извлечение, обогащение и квалификацию prospects. Начните с основ, затем постепенно добавляйте сложность: майнинг комментариев, детекция намерений, внешнее обогащение и предиктивная оценка.