Proxy ile Twitter/X Veri Kazıma Rehberi: API Kısıtlamaları Sonrası Kamu Verisine Erişim

X'in ücretsiz API erişimini kaldırmasından sonra, geliştiriciler kamu verisine erişmek için web kazımaya yöneliyor. Bu rehber, residential proxy'lerle güvenli, ölçeklenebilir Twitter/X kazıma stratejilerini açıklıyor.

Proxy ile Twitter/X Veri Kazıma Rehberi: API Kısıtlamaları Sonrası Kamu Verisine Erişim

X (eski adıyla Twitter), 2023 yılında ücretsiz API erişimini neredeyse tamamen kaldırdığında, sosyal medya izleme ve sentiment analizi yapan binlerce ekip ani bir duvara çarptı. Eskiden basit bir API anahtarıyla erişilebilen veriler artık aylık yüzlerce dolarlık ücretler gerektiriyor veya hiç erişilemez durumda. Bu nedenle, birçok geliştirici kamu verisine erişmek için web kazımaya yöneliyor.

Bu rehber, Twitter residential proxy kullanarak X'ten kamu verisini nasıl güvenli ve ölçeklenebilir şekilde çekebileceğinizi anlatıyor. Yasal çerçeveyi, teknik uygulamayı ve etik sınırları detaylıca ele alacağız.

Önemli Uyarı: Bu rehber yalnızca kamuya açık verilere erişim için eğitim amaçlıdır. X'in Hizmet Koşulları (Terms of Service) web kazımayı yasaklamaktadır. Herhangi bir kazıma faaliyeti başlatmadan önce hukuki danışmanlık almanız şarttır. ABD'de CFAA (Computer Fraud and Abuse Act), AB'de GDPR ve ilgili ülkelerin yasaları geçerlidir.

X API Kısıtlamaları Sonrası Manzara

2023 öncesinde, Twitter API'si üç katmanlıydı: ücretsiz, düşük maliyetli enterprise ve premium. Geliştiriciler ücretsiz katmanla günde binlerce tweet çekebilir, arama yapabilir ve trendleri takip edebilirdi. X'in Elon Musk tarafından satın alınmasından sonra bu değişti.

Mevcut X API Katmanları

KatmanAylık ÜcretTweet Çekme LimitiArama Erişimi
Free$0Yazma only (çekme yok)Yok
Basic$10010,000 tweet/aySınırlı
Pro$5,0001M tweet/ayTam
EnterpriseÖzel fiyatlandırmaÖzelTam

Bu fiyatlandırma, küçük startup'lar ve bağımsız geliştiriciler için API'yi erişilemez kıldı. Sonuç olarak, X/Twitter scraping faaliyetleri arttı — ancak bu da X'in anti-bot önlemlerini güçlendirmesine neden oldu.

X'in Anti-Kazıma Önlemleri

  • Rate limiting: Giriş yapmamış oturumlar için dakikada ~50-100 request
  • Datacenter IP engellemeleri: AWS, GCP, Azure ve bilinen VPN/Proxy sağlayıcı IP aralıkları otomatik flagleniyor
  • Browser fingerprinting: Canvas, WebGL, audio context parmak izi kontrolü
  • JavaScript challenge: Bot tespiti için client-side challenge'lar
  • GraphQL payload encryption: İçeriğe erişim için geçerli query hash'leri gerekiyor

Kamu Web Üzerinden Erişilebilen Veriler

X'in web arayüzü, giriş yapmadan bile önemli miktarda kamu verisi sunuyor. Ancak bu veriye erişmek, API'den çok daha karmaşık.

Giriş Olmadan Erişilebilen Veriler

  • Profil sayfaları: Kullanıcı adı, biyografi, takipçi/takip edilen sayıları, profil ve banner görselleri
  • Kamu tweet'leri: Korunmasız hesapların tüm tweet'leri, medya, beğeni/retweet/yorum sayıları
  • Tweet thread'leri: Bir tweet'in tüm yanıtları ve devam eden thread'ler
  • Arama sonuçları: Hashtag ve anahtar kelime aramaları (sınırlı sayıda sonuç)
  • Trending topics: Ülke/bölge bazlı trendler
  • Lists: Kamu listeler ve üyeleri

Giriş Gerektiren Veriler

  • Korumalı hesaplar: Tweet'ler ve profil detayları
  • Gelişmiş arama: Tarih aralığı, hesap türü filtreleri
  • Community notes: Bağlam notları (kısmen kamu)
  • Analytics: Görüntülenme, etkileşim detayları
  • DM'ler ve mention'lar: Kişisel mesajlar ve bildirimler

Kritik nokta: Giriş gerektiren verileri kazımak, hesap güvenliğinizi riske atar ve X ToS'u daha ağır ihlal eder. Bu rehber yalnızca giriş yapmadan erişilebilen kamu verisine odaklanıyor.

Neden Residential Proxy Gerekli?

Scrape Twitter with proxies araması yapan herkesin ilk karşılaştığı sorun, X'in datacenter IP'lerini agresif şekilde engellemesidir. Bir AWS veya DigitalOcean sunucusundan X'e istek atarsanız, muhtemelen 429 (Too Many Requests) veya 403 (Forbidden) alırsınız.

Datacenter vs Residential Proxy Karşılaştırması

ÖzellikDatacenter ProxyResidential Proxy
IP KaynağıVeri merkezi bloklarıGerçek ISP aboneleri
X Algılama RiskiÇok yüksekDüşük-orta
HızÇok hızlıOrta
MaliyetDüşük ($1-3/GB)Orta-yüksek ($5-15/GB)
Uzun ÖmürlülükIP'ler hızlı banlanırIP havuzu rotate edilebilir
Coğrafi HedeflemeSınırlıÜlke/sehir seviyesi

X'in IP Sınıflandırması

X, IP'leri üç kategoriye ayırıyor:

  1. Güvenilir (Residential ISP): Türk Telekom, Deutsche Telekom, Comcast gibi gerçek ISP'lerden gelen IP'ler. Normal rate limit uygulanır.
  2. Şüpheli (Hosting/VPN): AWS, Google Cloud, bilinen VPN sağlayıcılar. Sıkı rate limit, sık sık captcha.
  3. Engellenmiş (Known bot networks): Daha önce tespit edilmiş bot ağları. Anında 403.

Bu nedenle, Twitter residential proxies kullanmak, başarı oranını dramatik şekilde artırır. Her istekte farklı bir residential IP kullanarak, X'in IP bazlı throttling'ini aşabilirsiniz.

Python + Playwright ile X Kazıma Uygulaması

Şimdi, residential proxy havuzu kullanarak X'ten kamu verisi çeken çalışan bir Python örneği oluşturalım. Bu örnek Playwright kullanıyor çünkü X'in JavaScript-rendered SPA'sı için gerçek bir tarayıcı gerekiyor.

Kurulum

pip install playwright asyncio
playwright install chromium

Temel Profil Kazıma Kodu

import asyncio
from playwright.async_api import async_playwright
import json
import re

PROXY_HOST = "gate.proxyhat.com"
PROXY_PORT = 8080
PROXY_USER = "your-username-country-US"
PROXY_PASS = "your-password"

def get_proxy_url(session_id=None):
    """Her istek için farklı bir residential IP."""
    user = f"{PROXY_USER}-session-{session_id}" if session_id else PROXY_USER
    return f"http://{user}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}"

async def scrape_profile(username: str, session_id: str = None):
    """Bir X profil sayfasını kazır."""
    proxy_url = get_proxy_url(session_id)
    
    async with async_playwright() as p:
        browser = await p.chromium.launch(
            proxy={"server": proxy_url},
            headless=True
        )
        context = await browser.new_context(
            user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
            viewport={"width": 1920, "height": 1080}
        )
        page = await context.new_page()
        
        try:
            # Profil sayfasına git
            await page.goto(f"https://x.com/{username}", wait_until="networkidle")
            
            # X sayfadaki JSON veriyi <script> tag'i içinde embed eder
            # Bu veriyi extract edelim
            content = await page.content()
            
            # GraphQL response'u bul
            pattern = r'<script id="__NEXT_DATA__" type="application/json">([^<]+)</script>'
            match = re.search(pattern, content)
            
            if match:
                data = json.loads(match.group(1))
                return data
            
            # Alternatif: API intercept
            # X GraphQL endpoint'lerini dinleyebiliriz
            return None
            
        except Exception as e:
            print(f"Hata: {e}")
            return None
        finally:
            await browser.close()

# Kullanım
async def main():
    result = await scrape_profile("elonmusk", session_id="abc123")
    if result:
        print(json.dumps(result, indent=2)[:500])

asyncio.run(main())

GraphQL Intercept ile Tweet Çekme

X'in web arayüzü GraphQL API kullanıyor. Bu istekleri intercept ederek ham veriye ulaşabiliriz:

import asyncio
from playwright.async_api import async_playwright
import json

PROXY_HOST = "gate.proxyhat.com"
PROXY_PORT = 8080
PROXY_USER = "your-username-country-US"
PROXY_PASS = "your-password"

async def scrape_tweets_with_intercept(username: str, max_tweets: int = 20):
    """GraphQL isteklerini intercept ederek tweet'leri çeker."""
    proxy_url = f"http://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}"
    tweets = []
    
    async with async_playwright() as p:
        browser = await p.chromium.launch(
            proxy={"server": proxy_url},
            headless=True
        )
        context = await browser.new_context()
        page = await context.new_page()
        
        # GraphQL isteklerini dinle
        async def handle_response(response):
            if "graphql" in response.url and "UserTweets" in response.url:
                try:
                    data = await response.json()
                    if "data" in data:
                        entries = data.get("data", {}).get("user", {}).get("result", {}).get("timeline_v2", {}).get("timeline", {}).get("instructions", [{}])
                        for entry in entries:
                            if entry.get("type") == "TimelineAddEntries":
                                for item in entry.get("entries", []):
                                    tweet_data = item.get("content", {}).get("itemContent", {}).get("tweet_results", {}).get("result", {})
                                    if tweet_data:
                                        tweets.append({
                                            "id": tweet_data.get("rest_id"),
                                            "text": tweet_data.get("legacy", {}).get("full_text"),
                                            "created_at": tweet_data.get("legacy", {}).get("created_at"),
                                            "likes": tweet_data.get("legacy", {}).get("favorite_count"),
                                            "retweets": tweet_data.get("legacy", {}).get("retweet_count")
                                        })
                except:
                    pass
        
        page.on("response", handle_response)
        
        try:
            await page.goto(f"https://x.com/{username}", wait_until="networkidle")
            # Scroll to load more tweets
            for _ in range(3):
                await page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
                await asyncio.sleep(2)
            
            return tweets[:max_tweets]
        except Exception as e:
            print(f"Hata: {e}")
            return tweets
        finally:
            await browser.close()

# Kullanım
async def main():
    tweets = await scrape_tweets_with_intercept("nasa")
    print(f"Çekilen tweet sayısı: {len(tweets)}")
    for tweet in tweets[:5]:
        print(f"- {tweet['text'][:100]}...")

asyncio.run(main())

Node.js ile X Arama Kazıma

JavaScript/TypeScript ekosisteminde çalışanlar için Playwright ile Node.js örneği:

const { chromium } = require('playwright');

const PROXY_HOST = 'gate.proxyhat.com';
const PROXY_PORT = 8080;
const PROXY_USER = 'your-username-country-US';
const PROXY_PASS = 'your-password';

async function searchTweets(query, maxResults = 50) {
    const proxyUrl = `http://${PROXY_USER}:${PROXY_PASS}@${PROXY_HOST}:${PROXY_PORT}`;
    
    const browser = await chromium.launch({
        proxy: { server: proxyUrl },
        headless: true
    });
    
    const context = await browser.newContext({
        userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
    });
    
    const page = await context.newPage();
    const tweets = [];
    
    // GraphQL responses'ları intercept et
    page.on('response', async (response) => {
        if (response.url().includes('graphql') && response.url().includes('SearchTimeline')) {
            try {
                const data = await response.json();
                // Tweet'leri extract et
                const instructions = data?.data?.search_by_raw_query?.search_timeline?.timeline?.instructions || [];
                for (const instruction of instructions) {
                    if (instruction.type === 'TimelineAddEntries') {
                        for (const entry of instruction.entries || []) {
                            const tweetResult = entry?.content?.itemContent?.tweet_results?.result;
                            if (tweetResult) {
                                tweets.push({
                                    id: tweetResult.rest_id,
                                    text: tweetResult.legacy?.full_text,
                                    user: tweetResult.legacy?.user_screen_name,
                                    created_at: tweetResult.legacy?.created_at
                                });
                            }
                        }
                    }
                }
            } catch (e) {}
        }
    });
    
    try {
        const searchUrl = `https://x.com/search?q=${encodeURIComponent(query)}&src=typed_query`;
        await page.goto(searchUrl, { waitUntil: 'networkidle' });
        
        // Daha fazla sonuç için scroll
        for (let i = 0; i < 5; i++) {
            await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
            await page.waitForTimeout(2000);
        }
        
        return tweets.slice(0, maxResults);
    } catch (error) {
        console.error('Hata:', error);
        return tweets;
    } finally {
        await browser.close();
    }
}

// Kullanım
(async () => {
    const results = await searchTweets('#AI startups');
    console.log(`Bulunan tweet: ${results.length}`);
    results.slice(0, 3).forEach(t => console.log(`- @${t.user}: ${t.text?.slice(0, 80)}...`));
})();

Rate Limit Yönetimi

X, giriş yapmamış kullanıcılar için agresif rate limiting uygular. Bu sınırları aşmak için akıllı stratejiler gerekiyor.

X Rate Limit Türleri

Limit TürüEtkiAşma Stratejisi
IP-levelDakikada ~50-100 requestResidential proxy rotation
Session-levelSliding window algılamaYeni session başlatma
Account-levelHesap askıya almaGiriş yapmadan kazıma
BehavioralBot pattern tespitiHuman-like delays

429 Hatası Yönetimi

import asyncio
import random
from playwright.async_api import async_playwright

PROXY_HOST = "gate.proxyhat.com"
PROXY_PORT = 8080
PROXY_USER = "your-username"
PROXY_PASS = "your-password"

async def scrape_with_retry(url, max_retries=3):
    """429 hatalarını yöneten retry mantığı."""
    
    for attempt in range(max_retries):
        # Her denemede yeni session ve IP
        session_id = f"retry-{attempt}-{random.randint(1000, 9999)}"
        proxy_url = f"http://{PROXY_USER}-session-{session_id}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}"
        
        async with async_playwright() as p:
            browser = await p.chromium.launch(
                proxy={"server": proxy_url},
                headless=True
            )
            page = await browser.new_page()
            
            try:
                response = await page.goto(url, wait_until="networkidle")
                
                if response.status == 429:
                    print(f"Rate limited (attempt {attempt + 1}), yeni IP deneniyor...")
                    await asyncio.sleep(random.uniform(5, 15))
                    continue
                
                if response.status == 200:
                    content = await page.content()
                    return {"success": True, "content": content}
                
                print(f"HTTP {response.status}, retry...")
                
            except Exception as e:
                print(f"Hata: {e}")
            finally:
                await browser.close()
    
    return {"success": False, "error": "Max retries exceeded"}

# ProxyHat ile sticky session kullanarak
# uzun ömürlü IP'ler elde edebilirsiniz
async def scrape_with_sticky_session(urls):
    """Aynı IP ile birden fazla sayfa (sticky session)."""
    session_id = f"sticky-{random.randint(10000, 99999)}"
    proxy_url = f"http://{PROXY_USER}-session-{session_id}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}"
    
    async with async_playwright() as p:
        browser = await p.chromium.launch(
            proxy={"server": proxy_url},
            headless=True
        )
        context = await browser.new_context()
        
        results = []
        for url in urls:
            page = await context.new_page()
            try:
                await page.goto(url)
                await asyncio.sleep(random.uniform(2, 5))  # Human-like delay
                content = await page.content()
                results.append(content)
            finally:
                await page.close()
        
        await browser.close()
        return results

Sliding Window Tespiti

X, basit rate limit'in ötesinde sliding window pattern tespiti yapar. Kısa sürede çok istek atarsanız, IP'niz geçici olarak flaglenir. Bu nedenle:

  • Random delays: İstekler arasında 2-10 saniye rastgele bekleme
  • Burst avoidance: Ani istek patlamaları yerine sürekli, yavaş akış
  • Session rotation: Her 50-100 istekte yeni session/IP
  • Off-peak scraping: Yoğun olmayan saatlerde kazıma

Yasal Çerçeve ve Etik Kazıma

X/Twitter scraping konusu yasal olarak karmaşıktır. Son yıllarda görülen davalar ve mevcut durum şöyledir:

Önemli Davalar

  • hiQ Labs v. LinkedIn (2022): 9. Circuit, kamuya açık verilerin kazınmasının CFAA kapsamına girmediğine hükmetti. Bu, kamu verisi kazıma lehine bir emsal.
  • Meta v. Bright Data (2023): Meta, Bright Data'nın Facebook/Instagram kazımasına karşı dava açtı. Anlaşma ile sonuçlandı.
  • X v. Various scrapers: X, 2023'ten beri aktif olarak kazıma yapan şirketlere DMCA ve ToS ihlali bildirimleri gönderiyor.

X Hizmet Koşulları

X'in ToS'u açıkça şunları yasaklıyor:

  • "Crawling, scraping veya veri madenciliği"
  • "API olmayan yöntemlerle veri toplama"
  • "Otomatik sistemler kullanarak Platform'a erişim"

Bununla birlikte, ABD'de ToS ihlali tek başına cezai bir suç değil — ancak X hesabınızı yasaklayabilir, IP'nizi engelleyebilir ve sivil dava açabilir.

GDPR ve AB'de Kazıma

AB'de durum daha karmaşık:

  • Kişisel veri (tweet'ler, profil bilgileri) GDPR kapsamında
  • Kamu verisi olsa bile, işleme amacınız meşru olmalı
  • Araştırma gazetecilik amaçlı kazıma bazı AB ülkelerinde koruma altında

Kazıma vs API: Ne Zaman Hangisi?

KriterAPI KullanınKazıma Düşünün
Bütçe$5,000+/ay varsaBütçe kısıtlıysa
Veri hacmi1M+ tweet/ayDüşük hacim
GüncellikGerçek zamanlı gerekliGünlük/haftalık yeterli
Yasal riskMinimalOrta-yüksek
Teknik kaynakBasit REST entegrasyonuPlaywright/maintenance gerekli
Veri türüTüm API verisiSadece kamu verisi

En İyi Uygulamalar ve Öneriler

Teknik Öneriler

  1. Residential proxy kullanın: Datacenter IP'ler X'te anında engellenir. ProxyHat gibi kaliteli residential proxy sağlayıcıları kullanın.
  2. Gerçek tarayıcı kullanın: Requests/BeautifulSoup yeterli değil. Playwright veya Puppeteer şart.
  3. Rate limit saygılı olun: Dakikada 30-50 istekten fazlası risklidir.
  4. User-agent rotasyonu: Farklı tarayıcı parmak izleri kullanın.
  5. Hata yönetimi: 429, 403, captcha durumlarında graceful degradation.

Etik Öneriler

  1. robots.txt kontrolü: X robots.txt ile kazıma izni vermiyor — bunu bilerek hareket edin.
  2. Sadece kamu verisi: Korumalı hesapların verisine erişmeyin.
  3. Veri minimizasyonu: İhtiyacınız olandan fazla veri toplamayın.
  4. Saklama süresi: Veriyi gerekli süreden fazla tutmayın.
  5. Araştırma amacı: Akademik veya gazetecilik amaçlar daha iyi yasal koruma sağlayabilir.

Key Takeaways

Önemli Çıkarımlar:

  • X'in ücretsiz API'si kaldırıldı; Basic katman $100/ay ile başlıyor ve çok sınırlı.
  • Datacenter IP'ler X'te neredeyse her zaman engellenir — residential proxy şart.
  • Playwright ile GraphQL intercept, en güvenilir kazıma yöntemi.
  • 429 hataları için retry mantığı ve IP rotation mutlaka uygulayın.
  • X ToS kazımayı yasaklıyor — yasal riskleri değerlendirin, gerekiyorsa API kullanın.
  • GDPR kapsamında kişisel veri işleme kurallarına uyun.

Sonraki Adımlar

Eğer sosyal medya izleme dashboard'u veya sentiment analiz sistemi inşa ediyorsanız, doğru proxy altyapısı kritiktir. ProxyHat, X kazıma için optimize edilmiş residential proxy planları sunuyor. Ülke bazlı hedefleme ve sticky session desteği ile X'in anti-bot önlemlerini aşmanız kolaylaşır.

Daha fazla kaynak için:

Unutmayın: Kazıma, API erişimin olmadığı durumlarda bir çözüm olabilir, ancak her zaman yasal ve etik sınırlar içinde kalmalı.

Başlamaya hazır mısınız?

148+ ülkede 50M+ konut IP'sine AI destekli filtreleme ile erişin.

Fiyatlandırmayı GörüntüleKonut Proxy'leri
← Bloga Dön