bufgix / bufgix.github.io

1 stars 0 forks source link

Python ile Web Scraping 💪 #1

Open bufgix opened 4 years ago

bufgix commented 4 years ago

Günümüz interneti gerekli gereksiz her bilgiyi içeren büyük bir veri yığını. Bu yığın içersinide sadece bizim için gerekli bilgilere ulaşmak bazen oldukça önemli oluyor. Bu noktada web scraping konusunda yetenekli olamamız gerekiyor. Blogumun ilk içeriği olan bu yazıda elimden geldiğince bu konu üzerinde duracağım.

Web Scraping Nedir?

Web scraping'i türkçeye çevirmeye çalıştığımızda Web'i kazıma gibi bir anlamı çıkıyor. Esasında yaptığımız iş, bulma ve parçalama işlemi. Web scraping'i kavramak için bir web sayfasının nasıl görüntülendiğini anlamamız gerekiyor.

Web sayfaları html dediğimiz bir markup dili ile yazılıyor. Web tarayıcıları(Chrome, Firefox... gibi) bu html kodlarını sahip oldukları motorlar sayesinde anlamlı site görünümlerine dönüştürüyor. Eğer statik bir sitenin html kodunu elde edebilirseniz sayfa hakkında tüm yazılara, resimlere, bağlantılara ve diğer verilere ulaşabiliyorsunuz. Site içinde sadece kendi istediğimiz verileri çekme işlemine de Web Scraping deniyor.

Gelin isterseniz bu işlemi kodlayarak anlayalım. Web Scraping için Python'un iki tane güçlü kütüphanesi var. requests ve Beautiful Soup.

Kodlayalım

Öncelikle yukarda belirtiğim iki kütüphaneyi kuralım

$ pip install beautifulsoup4 requests

requests ile web sayfalarında istekte bulunup geriye dönen değerleri (ki bunlar genelde html formatında olur) okuyabiliceğiz.

beautifulsoup4 ile ise string formatında gelen html'de parçalama ve arama yapabileceğiz.

Bir seneryo üzerinden devam edelim. Seneryoya göre https://github.com/django sitesinden djangoya ait tüm repoları ve repolara ait bilgileri çekmek istiyoruz diyelim.

Aşama 1: Veri Kaynağının İncelenmesi

Amacımız işaretlediğim kısımları çekmek olsun. img

Bunun için öncelikle scrape etmek istediğimiz datanın unique(eşsiz) bir özeliğini bulmamız lazım. Bu bazen bir class ismi bazen id olabilir. Tarayıcınızın dev tool'u bu iş için bayağı uygun.

img

Görüldüğü gibi istediğimiz veriler repo-list class'ına sahip bir div elementinini içinde. Bu bilgiyi aklımızda tutalım ve kodlamay başlayalım.

Aşama 2: Veri Kaynağından Verilerin Çekilmesi

Python da bir sitenin html kaynak kodunu çekmek oldukça kolaydır.

import requests

res = requests.get("https://github.com/django")
source = res.text

print(source)
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link href="https://github.githubassets.com" rel="dns-prefetch" />
    <link href="https://avatars0.githubusercontent.com" rel="dns-prefetch" />
    <link href="https://avatars1.githubusercontent.com" rel="dns-prefetch" />
    <link href="https://avatars2.githubusercontent.com" rel="dns-prefetch" />
    <link href="https://avatars3.githubusercontent.com" rel="dns-prefetch" />
    <link href="https://github-cloud.s3.amazonaws.com" rel="dns-prefetch" />
    <link
      href="https://user-images.githubusercontent.com/"
      rel="dns-prefetch"
    />
    <link
      crossorigin="anonymous"
      href="https://github.githubassets.com/assets/frameworks-02a3eaa24db2bd1ed9b64450595fc2cf.css"
      integrity="sha512-hddDYPWR0gBbqLRmIZP242WMEiYsVkYI2UCYCVUHB4h5DhD2cbtFJYG+HPh21dZGb+sbgDHxQBNJCBq7YbmlBQ=="
      media="all"
      rel="stylesheet"
    />
    <link crossorigin="anonymous"
    href="https://github.githubassets.com/assets/site-1faffb77dc8b20778aa60ecb5998972e.css"
    integrity="sha512-8BxpFTogS...

    [....]
  </head>
</html>

Şimdi elimizdeki bu ham datayı BeautifulSoup kullanarak yontalım.

from bs4 import BeautifulSoup
import requests

res = requests.get("https://github.com/django")
source = res.text

soup = BeautifulSoup(source, "html.parser")

BeautifulSoup'un aldığı ikinci parametre olan html.parser, BeautifulSoup'a hangi tipde bir parse işlemi yapacağını bildiriyor. html.parser yerine daha hızlı olan lxml parserini de kullanabilirdik. Ama lxml paketini projeye dahil etmek gerekirdi. O yüzden Python'un kendi html.parser'inden devam edelim.

Şimdi yukarda bahsettiğimiz repo-list sınıfını kullanarak verileri parse edelim. Bunun için soup nesnesine .find() methodunu kullanacağız.

from bs4 import BeautifulSoup
import requests

res = requests.get("https://github.com/django")
source = res.text

soup = BeautifulSoup(source, "html.parser")
repo_list = soup.find('div', attrs={"class": "repo-list"})

print(repo_list.prettify())

.find() methodunun ilk parametresi source treede aranacak olan elemanın ismidir. Yani bir html element ismi alır. attrs ise o elementin hangi özelikleri olduğunu belirtmek için kullanılır. Biz burada classrepo-list olan bir div elementini scrape etmesini söyledik. Yani BeautifulSoup, kaynak kodda şu patterne sahip ilk gördüğü elementi geri dönürecektir.

<div class="repo-list">
  [...]
</div>

Sonuca baktığımızda istediğimizi elde ettiğimizi görüyoruz

<div class="org-repos repo-list">
 <ul>
  <li class="public source d-block py-4 border-bottom" itemprop="owns" itemscope="itemscope" itemtype="http://schema.org/Code">
   <div class="flex-justify-between d-flex">
    <div class="flex-auto">
     <h3 class="wb-break-all">
      [...]
</div>

Şimdi elimizdeki div'i biraz daha inceleyelim. Aradığımız veriler bu div'in her bir li elementinde tutuluyor.

img

Bu div içindeki tüm li leri çekmek için .find_all() methodunu kullanabiliriz. Bu method, bulduğu tüm elemenleri bir liste içinde geri döndürür.

soup = BeautifulSoup(source, "html.parser")
repo_list = soup.find('div', attrs={"class": "repo-list"})

repos = repo_list.find_all('li')
print(len(repos))
19

img

Bingo! Artık tüm li leri çektik. Geriye repoların isimlerini almak kaldı.

repos = repo_list.find_all('li')
for repo in repos:
    print(repo.find("a").text.strip())
django
djangoproject.com
channels
daphne
code.djangoproject.com
django-docs-translations
django-contrib-comments
django-localflavor
deps
asgiref
djangosnippets.org
channels_redis
django-docker-box
code-of-conduct
djangopeople
asgi_ipc
ticketbot
djangobench
django-box

Burada tüm li elemenlerinin içindeki a tagının içeriğine erişmek için .text'i kullandık. .text html taglarının içindeki veriyi string şeklinde verir.

Hadi bu çıktıyı daha güzel hale getirelim

from bs4 import BeautifulSoup
import requests
import re

res = requests.get("https://github.com/django")
source = res.text

soup = BeautifulSoup(source, "html.parser")
repo_list = soup.find('div', attrs={"class": "repo-list"})

repos = repo_list.find_all('li')
template = """
Isim: {name}
Aciklama: {desc}
Star: {star}
================"""
for repo in repos:
    name = repo.find("a").text.strip()
    desc = ""
    if repo.find('p'):  ## Eğer açıklama varsa
        desc = repo.find('p').text.strip()
    star = repo.find(href=lambda x: x and re.compile("stargazers").search(x)).text.strip()
    print(template.format(name=name, desc=desc, star=star))

Sonuç:

Isim: django
Aciklama: The Web framework for perfectionists with deadlines.
Star: 46,040
================

Isim: djangoproject.com
Aciklama: Source code to djangoproject.com
Star: 1,151
================

Isim: channels
Aciklama: Developer-friendly asynchrony for Django
Star: 4,141
================

Isim: daphne
Aciklama: Django Channels HTTP/WebSocket server
Star: 1,120
================

Isim: code.djangoproject.com
Aciklama: Configuration for Django's Trac instance (code.djangoproject.com)
Star: 33
================

Isim: django-docs-translations
Aciklama: Translations of the Django documentation. Questions and discussions happen on https://groups.google.com/forum/#!forum/django-i18n
Star: 37
================

Isim: django-contrib-comments
Aciklama: 
Star: 358
================

Isim: django-localflavor
Aciklama: Country-specific Django helpers, formerly of contrib fame
Star: 498
================
[...]

[...]

Isim: djangobench
Aciklama: Harness and benchmarks for evaluating Django's performance over time
Star: 165
================

Isim: django-box
Aciklama: VM to run the Django test suite. ARCHIVED Please use https://github.com/django/django-docker-box
Star: 60
================

Buradaki

    star = repo.find(href=lambda x: x and re.compile("stargazers").search(x)).text.strip()

kodu kafa karıştırıcı olabilir. Aslında yaptığı işlem çok basit. li elementini tutan repo nesnesinin .find methodu ile, eğer herhangi bir elementin href alanı "stargazers" stringini içeriyorsa onu geri döndürüyor. Bu da repoya verilen starları scrape etmemizi sağlıyor.

Bitirirken...

Doğruyu söylemek gerekirse buraya kadar anlattığım şeyler Web Scraping işlmeminin çok küçük bir kısmı. BeautifulSoup'un dökümantasyonunda keşfedilmeyi bekleyen daha o kadar çok şey varki. Bu işte biraz daha vakit geçirdiğinizde Web Scraping'in sadece BeautifulSoup ve requests'den ibaret olmadığını göreceksiniz. Karşınızda her zaman statik bir veri döndüren sayfalar gelmeyecek. Bazı sayfalar kendi içeriklerini javascript ile oluşturur. Ve anlamlı verilerin oluşması için bir tarayıcı motoruna ihtiyaç duyar. Böyle durumda geleneksel yöntemleri kullanarak (bizim requests ile kaynak kod çekmek gibi) elde ettiğiniz şey sadece Javascript'in bundle dosyası olacaktır muhtemelen. Bunun için arkada bir tarayıcı motoru ve sağladığı API'leri kullanmak gerekiyor. Mesela Selenium bu iş için oldukça uygundur.