import sys
import subprocess
import threading
import time
import random
import os
import queue
import tkinter as tk
from tkinter import ttk
# ==========================================
# 1. OTOMATİK KÜTÜPHANE KONTROL VE KURULUMU
# ==========================================
def install_dependencies():
required_packages = ["selenium==4.18.1", "pandas", "openpyxl"]
missing_packages = []
try:
import selenium
except ImportError:
missing_packages.append("selenium==4.18.1")
try:
import pandas
except ImportError:
missing_packages.append("pandas")
try:
import openpyxl
except ImportError:
missing_packages.append("openpyxl")
if missing_packages:
print(f"Eksik kütüphaneler tespit edildi, kuruluyor: {missing_packages}")
print("Lütfen bekleyin (1-2 dakika sürebilir)...")
subprocess.check_call([sys.executable, "-m", "pip", "install", "--upgrade", *missing_packages])
print("Kurulum tamamlandı. Uygulama başlatılıyor...")
install_dependencies()
import pandas as pd
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, NoSuchElementException
# ==========================================
# 2. SELENIUM SCRAPER MOTORU
# ==========================================
class LinkedInScraperEngine:
def __init__(self, log_queue):
self.log_queue = log_queue
self.driver = None
self.wait = None
self.employees_data = set()
self.next_pause_target = random.randint(30, 70)
self.next_save_target = random.randint(25, 65)
if not os.path.exists("data"):
os.makedirs("data")
def log(self, message):
self.log_queue.put(message)
def initialize_driver(self):
if self.driver is not None:
self.log("Tarayıcı zaten açık.")
return
self.log("Tarayıcı başlatılıyor... Lütfen bekleyin.")
options = webdriver.ChromeOptions()
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_argument("start-maximized")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option('useAutomationExte nsion', False)
prefs = {"profile.default_content_setting_values.notificat ions": 2}
options.add_experimental_option("prefs", prefs)
self.driver = webdriver.Chrome(options=options)
self.driver.execute_cdp_cmd("Page.addScriptToEvalu ateOnNewDocument", {
"source": """
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
})
"""
})
self.wait = WebDriverWait(self.driver, 15) # Bekleme süresi 15 saniyeye çıkarıldı
self.driver.get("https://www.linkedin.com/login")
self.log("Lütfen açılan tarayıcıda LinkedIn'e giriş yapın ve ardından arayüzden işlemleri seçin.")
def human_like_scroll(self, scroll_pauses=3):
try:
total_height = int(self.driver.execute_script("return document.body.scrollHeight"))
for i in range(1, scroll_pauses + 1):
scroll_to = int(total_height * (i / scroll_pauses))
self.driver.execute_script(f"window.scrollTo(0, {scroll_to});")
time.sleep(random.uniform(1.2, 2.5))
self.driver.execute_script("window.scrollBy(0, -300);")
time.sleep(random.uniform(1.0, 1.5))
except Exception as e:
self.log(f"Kaydırma sırasında uyarı: {e}")
def extract_name_parts(self, full_name):
if not full_name: return "", ""
clean_name = full_name.split('n')[0].strip()
parts = clean_name.split()
if len(parts) > 1:
return " ".join(parts[:-1]), parts[-1]
return clean_name, ""
def export_data_to_files(self, is_auto_save=False):
if not self.employees_data:
return
data_list = [{"Ad": emp[0], "Soyad": emp[1], "Sirket_ve_Unvan": emp[2]} for emp in self.employees_data]
df = pd.DataFrame(data_list)
df.drop_duplicates(subset=["Ad", "Soyad", "Sirket_ve_Unvan"], inplace=True)
excel_path = os.path.join("data", "linkedin_verileri.xlsx")
csv_path = os.path.join("data", "linkedin_verileri.csv")
df.to_excel(excel_path, index=False, engine='openpyxl')
df.to_csv(csv_path, index=False, encoding='utf-8-sig')
if not is_auto_save:
self.log(f"BAŞARILI! Toplam {len(df)} benzersiz kayıt dosyaya yazıldı.")
self.log(f"Dosya yolları:n{os.path.abspath(excel_path)}")
def check_anti_ban_and_save(self):
current_count = len(self.employees_data)
if current_count >= self.next_pause_target:
sleep_time = random.uniform(15.0, 20.0)
self.log(f"n--- RASTGELE DİNLENME MOLASI ---")
self.log(f"İnsan davranışı simüle ediliyor... {sleep_time:.1f} saniye bekleniyor.")
time.sleep(sleep_time)
self.next_pause_target = current_count + random.randint(30, 70)
self.log("Dinlenme bitti, okumaya devam ediliyor...n")
if current_count >= self.next_save_target:
self.log(f"n--- OTOMATİK ARA KAYIT ---")
self.log(f"Arka planda Excel'e kaydediliyor... (Toplam ulaşılan: {current_count} kişi)")
self.export_data_to_files(is_auto_save=True)
sleep_time = random.uniform(10.0, 20.0)
self.log(f"Kayıt sonrası {sleep_time:.1f} saniye dinleniliyor...")
time.sleep(sleep_time)
self.next_save_target = current_count + random.randint(25, 65)
self.log("Kayıt ve dinlenme tamamlandı, taramaya devam ediliyor...n")
def scrape_profile_and_sidebar(self):
if not self.driver:
self.log("HATA: Önce tarayıcıyı başlatmalısınız!")
return
self.log("Profil ve sağ panel taranıyor... Lütfen tarayıcıya dokunmayın.")
time.sleep(2) # Sayfanın ve DOM'un tam oturması için mutlak bekleme eklendi
added_count = 0
# Ana Profil (Daha esnek seçiciler ve WebDriverWait)
try:
self.wait.until(EC.presence_of_element_located((By .TAG_NAME, "h1")))
main_name = self.driver.find_element(By.TAG_NAME, "h1").text.strip()
try:
# 'text-body-medium.break-words' yerine daha geniş bir class
main_company = self.driver.find_element(By.CSS_SELECTOR, ".text-body-medium").text.strip()
except NoSuchElementException:
main_company = "Unvan/Şirket Bulunamadı"
f_name, l_name = self.extract_name_parts(main_name)
if f_name:
self.employees_data.add((f_name, l_name, main_company))
added_count += 1
self.log(f"Ana Profil Eklendi: {f_name} {l_name} | {main_company}")
except Exception as e:
self.log("Ana profil h1 etiketi bulunamadı. Sayfanın tam yüklendiğinden emin olun.")
self.human_like_scroll(scroll_pauses=4) # Yan panelin yüklenmesi için scroll
# Sağ Panel
try:
# Sadece 'aside' içindeki 'li' etiketlerini bul (En güvenli yöntem)
sidebar_elements = self.driver.find_elements(By.XPATH, "//aside//li")
for elem in sidebar_elements:
try:
# İsim etiketi için çoklu alternatif
name_elem = elem.find_element(By.CSS_SELECTOR, ".artdeco-entity-lockup__title, span[dir='ltr']")
name_text = name_elem.text.strip().split('n')[0]
try:
title_text = elem.find_element(By.CSS_SELECTOR, ".artdeco-entity-lockup__subtitle").text.strip()
except:
title_text = "Belirtilmemiş"
f_name, l_name = self.extract_name_parts(name_text)
# "LinkedIn Üyesi" veya reklam olabilecek girdileri engelle
if f_name and "LinkedIn" not in f_name:
before = len(self.employees_data)
self.employees_data.add((f_name, l_name, title_text))
if len(self.employees_data) > before:
added_count += 1
except NoSuchElementException:
continue
self.log(f"İşlem Tamam. Bu sayfadan toplam {added_count} yeni kişi eklendi.")
self.check_anti_ban_and_save()
except Exception as e:
self.log("Sağ panel taramasında veri bulunamadı veya panel yok.")
def scrape_company_people(self):
if not self.driver:
self.log("HATA: Önce tarayıcıyı başlatmalısınız!")
return
self.log("Şirket çalışanları taranıyor... Lütfen sayfaya müdahale etmeyin.")
time.sleep(2)
try:
self.wait.until(EC.presence_of_element_located((By .TAG_NAME, "h1")))
company_header = self.driver.find_element(By.TAG_NAME, "h1").text.strip()
except:
company_header = "Bilinmeyen Şirket"
# Şirket listesi sayfasında olduğumuzu teyit et
try:
self.wait.until(EC.presence_of_element_located((By .CSS_SELECTOR, ".artdeco-entity-lockup__title")))
except:
self.log("Uyarı: Ekranda çalışan kartı bulunamadı. Şirketin 'Kişiler' sekmesinde misiniz?")
return
last_height = self.driver.execute_script("return document.body.scrollHeight")
attempts = 0
while True:
try:
# Daha genel kart seçicileri
cards = self.driver.find_elements(By.CSS_SELECTOR, "li .artdeco-entity-lockup, .org-people-profile-card")
initial_count = len(self.employees_data)
for card in cards:
try:
name_text = card.find_element(By.CSS_SELECTOR, ".artdeco-entity-lockup__title, span[dir='ltr']").text.strip()
try:
subtitle_text = card.find_element(By.CSS_SELECTOR, ".artdeco-entity-lockup__subtitle").text.strip()
except:
subtitle_text = company_header
f_name, l_name = self.extract_name_parts(name_text)
if f_name and "LinkedIn" not in f_name:
self.employees_data.add((f_name, l_name, subtitle_text))
except NoSuchElementException:
continue
new_count = len(self.employees_data)
if new_count == initial_count:
attempts += 1
else:
attempts = 0
self.log(f"Tarama devam ediyor... Anlık toplam kişi: {new_count}")
self.check_anti_ban_and_save()
if attempts >= 3:
self.log("Listenin sonuna gelindi veya yeni veri yüklenmiyor. Tarama bitti.")
break
self.human_like_scroll(scroll_pauses=2)
new_height = self.driver.execute_script("return document.body.scrollHeight")
if new_height == last_height:
attempts += 1
else:
last_height = new_height
except Exception as e:
self.log(f"Tarama sırasında bir hata oluştu: {e}")
break
def export_and_close(self):
if not self.employees_data:
self.log("Dışa aktarılacak veri bulunamadı. Tarayıcı kapatılıyor.")
else:
self.log("Tüm işlemler bitti. Final kayıtlar Excel ve CSV'ye işleniyor...")
self.export_data_to_files(is_auto_save=False)
if self.driver:
self.driver.quit()
self.driver = None
self.log("Tarayıcı güvenli bir şekilde kapatıldı.")
# ==========================================
# 3. KULLANICI ARAYÜZÜ (GUI)
# ==========================================
class LinkedInScraperGUI:
def __init__(self, root):
self.root = root
self.root.title("LinkedIn Bot & Ağ Gezgini")
self.root.geometry("650x550")
self.root.configure(padx=20, pady=20)
self.log_queue = queue.Queue()
self.scraper = LinkedInScraperEngine(self.log_queue)
self.create_widgets()
self.root.after(100, self.process_log_queue)
def create_widgets(self):
info_label = ttk.Label(self.root, text="LinkedIn Veri Çekme Otomasyonu", font=("Arial", 14, "bold"))
info_label.pack(pady=(0, 15))
btn_frame = ttk.Frame(self.root)
btn_frame.pack(fill=tk.X, pady=10)
self.btn_start = ttk.Button(btn_frame, text="1. Tarayıcıyı Başlat ve Giriş Yap", command=self.start_browser_thread)
self.btn_start.pack(fill=tk.X, pady=5)
self.btn_profile = ttk.Button(btn_frame, text="2. Açık Olan Kişi Profilini ve Sağ Paneli Tara", command=self.scrape_profile_thread)
self.btn_profile.pack(fill=tk.X, pady=5)
self.btn_company = ttk.Button(btn_frame, text="3. Açık Olan Şirketin Çalışanlarını Tara", command=self.scrape_company_thread)
self.btn_company.pack(fill=tk.X, pady=5)
self.btn_export = ttk.Button(btn_frame, text="4. Verileri Kaydet ve Uygulamayı Kapat", command=self.export_thread)
self.btn_export.pack(fill=tk.X, pady=5)
log_label = ttk.Label(self.root, text="İşlem Kayıtları (Log):")
log_label.pack(anchor=tk.W, pady=(15, 5))
self.log_text = tk.Text(self.root, height=12, state='disabled', bg="#f0f0f0", font=("Consolas", 9))
self.log_text.pack(fill=tk.BOTH, expand=True)
def write_log(self, message):
self.log_text.config(state='normal')
self.log_text.insert(tk.END, f"> {message}n")
self.log_text.see(tk.END)
self.log_text.config(state='disabled')
def process_log_queue(self):
try:
while True:
msg = self.log_queue.get_nowait()
self.write_log(msg)
except queue.Empty:
pass
finally:
self.root.after(100, self.process_log_queue)
def start_browser_thread(self):
threading.Thread(target=self.scraper.initialize_dr iver, daemon=True).start()
def scrape_profile_thread(self):
threading.Thread(target=self.scraper.scrape_profil e_and_sidebar, daemon=True).start()
def scrape_company_thread(self):
threading.Thread(target=self.scraper.scrape_compan y_people, daemon=True).start()
def export_thread(self):
def task():
self.scraper.export_and_close()
self.log_queue.put("Tüm işlemler bitti. Pencereyi kapatabilirsiniz.")
threading.Thread(target=task, daemon=True).start()
if __name__ == "__main__":
root = tk.Tk()
style = ttk.Style()
if "clam" in style.theme_names():
style.theme_use("clam")
app = LinkedInScraperGUI(root)
def on_closing():
if app.scraper.employees_data:
print("nPencere kapatıldı. Veriler otomatik olarak Excel'e yedekleniyor...")
try:
app.scraper.export_data_to_files(is_auto_save=Fals e)
print("Kayıt başarılı!")
except:
pass
if app.scraper.driver:
try:
app.scraper.driver.quit()
except:
pass
root.destroy()
root.protocol("WM_DELETE_WINDOW", on_closing)
root.mainloop()