Laravel Türkiye Discord Kanalı Forumda kod paylaşılırken dikkat edilmesi gerekenler!Birlikte proje geliştirmek ister misiniz?
  • Genel
  • Türkiye Güncel Şehir İlçe Ve Mahalle Verileri Çekmek İçin Scraping Kodu

Bir Command oluşturarak kullanabilirsiniz. ptt den direk bütün Şehir İlçe ve ilçelerin mahallerini oluşturan bir json çıktısı veriyor isterseniz çıktıyı db'ye de kaydedebilirsiniz. Umarım İşinize yarar

https://github.com/koti42/turkish-citizen-list-api

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
use Illuminate\Support\Facades\Storage;

class ScrapePttAddress extends Command
{
    protected $signature = 'scrape:ptt-address';
    protected $description = 'PTT posta kodu sisteminden tüm adres verilerini çeker';

    private $baseUrl = 'https://postakodu.ptt.gov.tr';
    private $client;
    private $cookieJar;
    private $totalProvinces = 0;
    private $currentProvince = 0;
    private $totalDistricts = 0;
    private $currentDistrict = 0;
    private $totalNeighborhoods = 0;

    public function __construct()
    {
        parent::__construct();
        $this->cookieJar = new CookieJar();
        $this->client = new Client([
            'cookies' => $this->cookieJar,
            'headers' => [
                'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0',
                'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
                'Content-Type' => 'application/x-www-form-urlencoded; charset=UTF-8'
            ]
        ]);
    }

    public function handle()
    {
        try {
            $this->output->title('PTT Adres Verisi Çekme İşlemi Başlatılıyor');
            $addressData = [];

            $response = $this->client->get($this->baseUrl);
            $html = (string) $response->getBody();

            if (!preg_match('/MainContent_DropDownList1".*?>(.*?)<\/select>/s', $html, $provinceSelect)) {
                throw new \Exception('İl listesi bulunamadı');
            }

            preg_match_all('/<option value="(\d+)">([^<]+)<\/option>/', $provinceSelect[1], $matches, PREG_SET_ORDER);

            $this->totalProvinces = count($matches) - 1;

            $mainProgress = $this->output->createProgressBar($this->totalProvinces);
            $mainProgress->setFormat(
                "\n%current%/%max% [%bar%] %percent:3s%%\n" .
                "%message%\n"
            );

            $mainProgress->start();

            foreach ($matches as $match) {
                if ($match[1] == '-1') continue;

                $provinceId = $match[1];
                $provinceName = $this->cleanText($match[2]);

                $mainProgress->setMessage("<fg=blue>🏢 {$provinceName} ili işleniyor...</>");

                preg_match('/__VIEWSTATE" value="([^"]+)"/', $html, $viewstateMatch);
                preg_match('/__EVENTVALIDATION" value="([^"]+)"/', $html, $eventvalidationMatch);

                $response = $this->client->post($this->baseUrl, [
                    'form_params' => [
                        '__EVENTTARGET' => 'ctl00$MainContent$DropDownList1',
                        '__EVENTARGUMENT' => '',
                        '__VIEWSTATE' => $viewstateMatch[1],
                        '__EVENTVALIDATION' => $eventvalidationMatch[1],
                        'ctl00$MainContent$DropDownList1' => $provinceId
                    ]
                ]);

                $districtHtml = (string) $response->getBody();

                if (!preg_match('/MainContent_DropDownList2".*?>(.*?)<\/select>/s', $districtHtml, $districtSelect)) {
                    continue;
                }

                preg_match_all('/<option value="(\d+)">([^<]+)<\/option>/', $districtSelect[1], $districtMatches, PREG_SET_ORDER);

                $this->totalDistricts = count($districtMatches) - 1;
                $this->currentDistrict = 0;

                $districtProgress = $this->output->createProgressBar($this->totalDistricts);
                $districtProgress->setFormat(
                    "  %current%/%max% [%bar%] %percent:3s%%\n" .
                    "  %message%\n"
                );
                $districtProgress->start();

                $currentProvince = [
                    'il_id' => $provinceId,
                    'il_adi' => $provinceName,
                    'ilceler' => []
                ];

                foreach ($districtMatches as $district) {
                    if ($district[1] == '-1') continue;

                    $districtId = $district[1];
                    $districtName = $this->cleanText($district[2]);

                    $districtProgress->setMessage("<fg=yellow>🏠 {$districtName} ilçesi işleniyor...</>");

                    $neighborhoods = $this->getNeighborhoods($districtHtml, $provinceId, $districtId);

                    $currentProvince['ilceler'][] = [
                        'ilce_id' => $districtId,
                        'ilce_adi' => $districtName,
                        'mahalleler' => $neighborhoods
                    ];

                    if (empty($addressData)) {
                        $addressData[] = $currentProvince;
                    } else {
                        $lastIndex = count($addressData) - 1;
                        if ($addressData[$lastIndex]['il_id'] === $provinceId) {
                            $addressData[$lastIndex] = $currentProvince;
                        } else {
                            $addressData[] = $currentProvince;
                        }
                    }

                    Storage::put(
                        'ptt_address_data.json',
                        json_encode($addressData, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)
                    );

                    $this->currentDistrict++;
                    $districtProgress->advance();
                    sleep(1);
                }

                $districtProgress->finish();
                $mainProgress->advance();
                sleep(2);
            }

            $mainProgress->finish();

            $this->output->success(sprintf(
                "\n\nİşlem tamamlandı!\n" .
                "Toplam %d il, %d ilçe ve %d mahalle verisi çekildi.",
                $this->totalProvinces,
                $this->totalDistricts * $this->totalProvinces,
                $this->totalNeighborhoods
            ));

        } catch (\Exception $e) {
            $this->error('Hata: ' . $e->getMessage());
            return Command::FAILURE;
        }
    }

    private function getNeighborhoods($districtHtml, $provinceId, $districtId)
    {
        preg_match('/__VIEWSTATE" value="([^"]+)"/', $districtHtml, $newViewstateMatch);
        preg_match('/__EVENTVALIDATION" value="([^"]+)"/', $districtHtml, $newEventvalidationMatch);

        $response = $this->client->post($this->baseUrl, [
            'form_params' => [
                '__EVENTTARGET' => 'ctl00$MainContent$DropDownList2',
                '__EVENTARGUMENT' => '',
                '__VIEWSTATE' => $newViewstateMatch[1],
                '__EVENTVALIDATION' => $newEventvalidationMatch[1],
                'ctl00$MainContent$DropDownList1' => $provinceId,
                'ctl00$MainContent$DropDownList2' => $districtId
            ]
        ]);

        $neighborhoodHtml = (string) $response->getBody();
        $neighborhoods = [];

        if (preg_match('/MainContent_DropDownList3".*?>(.*?)<\/select>/s', $neighborhoodHtml, $neighborhoodSelect)) {
            preg_match_all('/<option value="([^"]+)">([^<]+)<\/option>/', $neighborhoodSelect[1], $neighborhoodMatches, PREG_SET_ORDER);

            foreach ($neighborhoodMatches as $neighborhood) {
                if ($neighborhood[1] == '-1') continue;

                $neighborhoodName = $this->cleanText($neighborhood[2]);

                preg_match('/(\d{5})/', $neighborhoodName, $postalCodeMatch);
                $postaKodu = $postalCodeMatch[1] ?? null;

                $neighborhoodName = preg_replace('/\s*\/\s*.*$/', '', $neighborhoodName);

                $neighborhoods[] = [
                    'mahalle_id' => $this->cleanId($neighborhood[1]),
                    'mahalle_adi' => $neighborhoodName,
                    'posta_kodu' => $postaKodu
                ];

                $this->totalNeighborhoods++;
                $this->line("    <fg=green>📍 {$neighborhoodName} mahallesi eklendi.</>");
            }
        }

        return $neighborhoods;
    }

    private function cleanText($text)
    {
        $text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');

        // Başındaki ve sonundaki boşlukları temizle
        $text = trim($text);

        $text = preg_replace('/\s+/', ' ', $text);

        $text = str_replace([
            '&#214;', '&#220;', '&#199;', '&#286;', '&#304;', '&#350;',
            'Ö', 'Ü', 'Ç', 'Ğ', 'İ', 'Ş', 'ö', 'ü', 'ç', 'ğ', 'ı', 'ş'
        ], [
            'O', 'U', 'C', 'G', 'I', 'S',
            'O', 'U', 'C', 'G', 'I', 'S', 'o', 'u', 'c', 'g', 'i', 's'
        ], $text);

        return $text;
    }

    private function cleanId($id)
    {
        $id = str_replace(['\\', '/'], '_', $id);

        $id = preg_replace('/[^a-zA-Z0-9_]/', '', $id);

        return $id;
    }
}
3 ay sonra

Merhabalar hocam, yöntem hala çalışıyor mu?
Ayrıca bahsettiğiniz çekme yöntemi ile ilgili bir ufak bir video mevcut mudur? Hic kod bilgim bulunmuyorda. Bir sitemde de bu verilere ihtuyacim mevcut.

    Parabura Merhaba Evet çalışıyor. Github adresin de detaylıca nasıl yapman gerektiğini anlattım. Takılırsan yine yardımcı olurum

      koti42 merhabalar hocam. Aslında Githup paylaşımınızı da gördüm de kodlama bilgim olmadığı için ne yapacağımı anlayamadım. Teşekkür ederim yine de cevabınız için.

        Parabura laravel kurduysan gerisi verdiğim kodu terminale verip command oluşturmak oluşan commandin içine verdiğim kodu ekleyip çalıştırabilirsin ek olarak çalıştırılmis verilerin olduğu dosya da github da mevcut uğraşmak istemezsen

        Teşekkür ederim sonunda uzun uğraşlar sonucunda çalıştırdım. Ama dikkati mi çeken bir yer var hocam.
        Türkçe karakter olan yerlerde aşağıdaki gibi görünüm oluyor. Bunu nasıl düzeltebilirim?
        "mahalle_id": "252",
        "mahalle_adi": "GİREĞİYENİK&#214;Y MAH",
        "posta_kodu": "01722"
        },
        {
        "mahalle_id": "102",
        "mahalle_adi": "G&#214;K&#199;EK&#214;Y MAH",
        "posta_kodu": "01722"

          Parabura Genelde hata almamak adına türkçe karakterleri ingilizce karakterlere dönüştürme yapmıştım kodun için de
          https://raw.githubusercontent.com/koti42/turkish-citizen-list-api/refs/heads/main/turkish-citizen buradan bakabilirsen eğer paylaştığın aynı mahalle doğru görüntüleniyor

          Daha gelişmiş bir versiyonunu yapmıştım kendi projem için ama onu paylaşamıyorum. Bu konuda AI dan destek alabilirsin istersen türkçe kullanacaksan

             {
                                  "mahalle_id": "25_2",
                                  "mahalle_adi": "GIREGIYENIKOY MAH",
                                  "posta_kodu": "01722"
                              },

          Teşekkür ederim Türkçe olarak kullanacağım hocam. Bu sebeple dediğiniz gibi Aı'den destek alsam iyi olacak.

          Hocam peki aşağıdaki alanda gördüğünüz üzere normalde arka planda Ankara ilçeleri ve mahalle verisi çekilirken Amasya çekiliyormuş gibi görünüyor. Bunu nasıl düzeltebilirim? Hangi il çekiliyorsa o il ile senkron şekilde ilerlese.

          PTT Adres Verisi Çekme İşlemi Başlatılıyor

          0/80 [>---------------------------] 0%

          1/80 [>---------------------------] 1%

          2/80 [>---------------------------] 2%

          3/80 [=>--------------------------] 3%

          4/80 [=>--------------------------] 5%

          5/80 [=>--------------------------] 6%

          6/80 [==>-------------------------] 7%
          AMASYA ili işleniyor...
          5/24 [=====>----------------------] 20%
          BEYPAZARI ilçesi işleniyor...

            Parabura bu ptt den gelen bir veri akışı olduğu için sadece orası bilgilendirme olarak olusuyor