PasteBin'i biliyorsunuz, en çok kullanılan snippet sitelerinden biri. Yeni snippet eklerken iki modu var; private ve public. Private olarak eklenen kayıtlar site arşivinde listelenmiyor, public olarak eklenenler de belirli bir süre (yenileri eklenene kadar) arşiv bölümünden görüntülenebiliyor. Bu sayede aradığımız bilgileri eklenen snippetleri inceleyip filtreleyerek elde edebiliyoruz.

Veri Madenciliği: PasteBin Toplayıcı (Python)

Aslında bu sitenin bir hatası değil, kullanıcıların dikkatsizliği. Yani göndereceği bilgiye sadece linklerle erişebiliyor olduğunu sanıyor olmalılar. Ya da sadece acele ederken unutuyorlar.

Bilgilerin çoğu çöp evet, fakat arada işe yarar bilgiler de çıkabiliyor. Bir, iki günlük takiple mail adresleri (ve şifreleri), telefon numaraları, gerçek kişilere ait adresler, kredi kartı numaraları gibi bilgiler elde edebildim. Fakat tabi ki hiçbirini kullanmadım, sadece test amaçlıydı.

Arşivdeki kayıtlar kalıcı değil, yenileri eklendikçe eskiler listeden kaldırılıyor. Bu yüzden yazacağımız script açık olduğu sürece bilgiler takip edilebilecek. İstediğimiz türdeki bilgiyi düzeli ifadeler yardımıyla filtreleyip gereksiz bilgileri kaydetmeyeceğiz. Yani her anlamda bir "madenci" yapacağız.

Hazırsanız başlayalım.

Neler kullanacağız?

Kullandığım modülleri altta listeledim, şimdilik siz de aynı importları yapabilirsiniz.

import re
import requests
import time
import sqlite3
from bs4 import BeautifulSoup
import phonenumbers

Bilgiyi filtrelemek için re, sitenin içeriğini almak için requests, veri çekme zamanlaması için time, ayıkladığımız verileri kaydetmek için sqlite3, çektiğimiz veriyi ayrıştırmak için bs4 kullanacağız. Bu örnekte telefon numaralarını alacağım için phonenumbers modülünü de kullandım. Siz istediğinize göre kullanmayabilirsiniz.

PastebinMiner Nesnesi

Önce kullanacağımız sınıfı oluşturalım. Ardından ihtiyacımız olan tüm fonksiyonları yazacağız.

class PastebinMiner:
    def __init__(self, path):
        self.data = []
        self.source = "http://pastebin.com/archive"
        self.connection = sqlite3.connect(path)
        self.cursor = self.connection.cursor()

Başlangıçta parametre olarak sqlite veritabanının yolunu alıyoruz. Sabit pastebin arşiv adresini belirledikten ve aldığımız veriyi saklamak için bir liste (data) oluşturduktan sonra sqlite bağlantısını oluşturuyoruz.

Sınıfın içinde iki tane değişken var. İlki programın çalışmasını kontrol eden mining, ikincisi veriyi çekerken normal bir tarayıcıyı taklit etmek için gereken user-agent bilgisini içeren headers sözlüğü. Eğer benimkini kullanmak istemiyorsanız kendinizinkini öğrenebilirsiniz.

    mining = True
    headers = {"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:36.0) Gecko/20100101 Firefox/36.0"}

Şimdi basitçe izleme işini başlatan start fonksiyonunu ekleyelim.

    def start(self):
        self.mine()

Yaşam Döngüsü

Olayın döndüğü mine fonksiyonunu yazalım. Aşama aşama açıklayacağım.

    def mine(self):
        while self.mining:
            response = requests.get(self.source, headers=self.headers).content
            soup = BeautifulSoup(response)
            soup.prettify()

Az önce tanımladığımız mining değişkenine göre teorik olarak sonsuza kadar dönen bir döngümüz var. Hemen içinde requests modülü ile az önce tanımladığımız source ve header bilgilerini kullanarak safa içeriğini alıyoruz.

Ardından aldığımız HTML kodları ile BeatifulSoup nesnesi oluşturup, prettify fonksiyonu ile kodları düzenli hale getiriyoruz.

Şimdi hemen altta iç içe iki tane for döngüsü oluşturalım.

            for table in soup.findAll('table', {'class': 'maintable'}):
                for a in table.findAll('a', href=True):
                    id = a['href'][1:]
                    if not id.startswith("archive") and not id in self.data:
                        print("# GET: {}".format(id)),

                        content = requests.get("http://pastebin.com/raw.php?i={}".format(id), headers=self.headers).content

Döngülerden ilki sayfa içinde class özelliği maintable olan tabloları alıyor. İçteki de o tablo içindeki href özelliği verilmiş olan linkleri topluyor.

Arşiv sayfasının kodlarını incelerseniz göreceksiniz ki, linklere snippetlerin ID numaraları verilmiş. Bu yüzden zahmetsizce toplayabiliyoruz. Hemen altındaki if koşulu ise alınan linkin "archive" ile başlamayıp (yani geçerli olup), alınan ID numarasının yukarıda tanımladığımız data sözlüğünde bulunmamasını (yani ilk defa analiz edilecek olmasını) kontrol ediyor.

Site içindeki raw.php dosyası ID numarası verilen kaydı metin belgesi olarak bize sunuyor. Yani extra regexp kullanmadan bu bilgiyi de almış oluyoruz.

Snippet içeriğini aldığımıza göre şimdi filtrelemeye başlayalım.

                        # search for pattern
                        regex = re.compile(r"(\+?\d{2}\s?\d{3}\s?\d{3}\s?\d{2}\s?\d{2})")
                        matches = regex.findall(content)

Benim kullandığım desen kabaca geçerli telefon numaralarını bulmak için. Daha iyisi muhtemelen yazılabilir, ama şimdilik benim işimi görüyor. Eğer başka tür bir bilgi için çalışıyorsanız ona uygun bir desen hazırlamalısınız.

Deseni derleyip (compile) daha önce siteden çektiğimiz content içindeki eşleşmeleri matches değişkenine attık.

Şimdi bulunan eşleşmeleri istediğimiz gibi kontrol edip, işe yarayanları kaydedelim.

                        if matches:
                            print(" | Found")

                            for match in matches:
                                self.data.append(id)
                                print("@ {}".format(match)),

Eğer matches boş değilse ekrana mesaj yazdırıp bir döngü ile tüm eşleşmeleri dolaşıp ekrana yazdırdık. Kaydın ID numarasını da data listesine ekledik.

Şimdi yakaladığımız bilgilerin geçerliliğini kontrol edeceğiz.

                                try:
                                    number = phonenumbers.parse(match, "TR")
                                    if phonenumbers.is_valid_number(number):
                                        self.cursor.execute("INSERT INTO tb_gsm(paste_id, gsm_no) VALUES ('{}', '{}')".format(id, match))
                                        self.connection.commit()
                                    else:
                                        print(" - Not Valid")
                                except phonenumbers.phonenumberutil.NumberParseException, e:
                                    print("{}".format(e.args[0]))

Telefon numarasının geçerliliğini phonenumbers içindeki parse fonksiyonuna numarayla beraber "TR" ifadesini yollayayacağız. Eğer numara geçersiz ise bu satırda NumberParseException hatası fırlatacak, try-except bloğu bunun için.

Daha sonra is_valid_number fonksiyonu ile numara formatının doğru olup olmadığını kontrol edip eğer doğruysa veritabanına ekledik. Benim kullandığım tabloda üç alan var; id, paste_id ve gsm_no. Eklerken kayıt numarasını paste_id alanına kaydediyorum ki, dilediğimde aynı kaydı açıp işe yarar farklı bilgiler var mı diye bakabileyim. Anlaşıldığı üzere execute fonksiyonu veritabanında sorgu işletmeye, commit fonksiyonu işlemi kaydetmeye yarıyor.

Koşulun içeriği bu kadar, eğer eşleşme yoksa kullanıcıya mesaj verip sleep fonksiyonu ile döngünün devam etmeden önce bir saniye beklemesini sağladık.

Sınıfın içeriği bu kadar. Artık sınıftan nesneyi türetip aramayı başlatabiliriz.

if __name__ == "__main__":
    miner = PastebinMiner("data.db")
    miner.start()

Acelesi Olanlara

Geçerli telefon numarası bulmak (üstelik Türkiye için) oldukça zaman alıyor. Eğer betiği fazla beklemeden test etmek istiyorsanız mail adresi gibi çok bulunan bir desen (pattern) kullanabilirsiniz.

İstediğiniz kadar veri topladıktan sonra veritabanında "SELECT * FROM tb_gsm" gibi bir sorguyla verilere erişebilirsiniz. Ya da en baştan ben uğraşmayayım SQL ile falan derseniz execute edilen yerde veriyi metin belgesine falan yazdırabilirsiniz. Tabi daha çok yer kaplar.

Gene mi Amcalar?

Tabi ki bu yaptığımız işlemin hiç bir kötü amacı yok, sadece kendimizi geliştirmek amacıyla yapıyoruz. Betiği çocukların erişebileceği yerleden uzak tutuyoruz.

End Of Fun

İşte bu kadar, isterseniz şuradan betiğin çalışan halina göz atabilirsiniz.

Okuyup yorum yapmayanın koduna bug girsin, derleyicisi önüne aksın, kullandığı kütüphaneye dökümantasyon bulamasın. pyAmin!

Kolay gelsin!