[{"data":1,"prerenderedAt":1595},["ShallowReactive",2],{"sanity-7436qw":3},{"author":4,"body":23,"categories":1588,"mainImage":1589,"publishedAt":1592,"related":1593,"title":1594},{"bio":5,"image":17,"name":22},[6],{"_key":7,"_type":8,"children":9,"markDefs":15,"style":16},"d66c98b743ce","block",[10],{"_key":11,"_type":12,"marks":13,"text":14},"3aaa77625a97","span",[],"Teknoloji ve stratejiyi bir araya getiren, projelerini bir adım öteye taşımak için sürekli yeni yöntemler keşfeden bir dijital uygulayıcı. Dijital süreçleri merakla takip ediyor ve faydalı çözümler üretmek için çalışıyor",[],"normal",{"_type":18,"asset":19},"image",{"_ref":20,"_type":21},"image-c44a26e16c36665c00b5834c6185cac256958e10-800x800-webp","reference","Fehu-Zone",[24,81,117,126,135,157,169,189,201,213,221,237,249,257,268,292,300,319,338,350,358,362,371,379,387,392,401,409,417,420,428,436,444,447,455,463,466,482,490,498,501,515,523,531,540,548,556,559,567,575,578,586,594,602,630,646,654,661,665,673,680,688,696,716,740,748,768,784,792,807,815,823,831,847,855,862,866,893,900,904,911,915,923,930,938,954,962,978,993,1009,1025,1048,1068,1080,1088,1096,1100,1108,1111,1119,1127,1130,1138,1141,1149,1157,1160,1168,1176,1184,1192,1200,1222,1230,1238,1246,1249,1257,1260,1268,1276,1279,1287,1295,1303,1311,1314,1322,1330,1338,1350,1358,1366,1374,1382,1390,1398,1414,1422,1430,1438,1447,1459,1471,1483,1495,1507,1519,1538,1546,1554,1562,1570,1578],{"_key":25,"_type":8,"children":26,"markDefs":77,"style":16},"4b767b3ba22a",[27,31,37,41,45,49,53,57,61,65,69,73],{"_key":28,"_type":12,"marks":29,"text":30},"6c1ccac22654",[],"Bu yazıda, dünya nüfusu verilerini ",{"_key":32,"_type":12,"marks":33,"text":36},"4d28a29a45b4",[34,35],"578de9b3f1a6","strong","Worldometer",{"_key":38,"_type":12,"marks":39,"text":40},"6d14b2e510a6",[]," üzerinden otomatik olarak çekip, ",{"_key":42,"_type":12,"marks":43,"text":44},"8d1a06880e12",[35],"Elasticsearch’e",{"_key":46,"_type":12,"marks":47,"text":48},"0a424017ae34",[]," kaydederek ",{"_key":50,"_type":12,"marks":51,"text":52},"6dfa0fe7e543",[35],"Kibana",{"_key":54,"_type":12,"marks":55,"text":56},"1e9cc0d6bd44",[]," ile görselleştirdiğim dashboardları web sitesine gömüp canlıya aldığım projemi detaylarıyla paylaşıyorum. Projeye Python ile başladım ancak projemi canlıya aldığımda ",{"_key":58,"_type":12,"marks":59,"text":60},"89954dec30d0",[35],"Python’un inanılmaz kaynak tükettiğini farkettim ve Javascript’e geçtim fakat siz isterseniz pyrhon kodlarını da inceleyebilirsiniz.",{"_key":62,"_type":12,"marks":63,"text":64},"da5d7b6813c3",[]," Kodları birebir ",{"_key":66,"_type":12,"marks":67,"text":68},"78dd1857e893",[35],"GitHub",{"_key":70,"_type":12,"marks":71,"text":72},"b250c7ec153c",[]," üzerinden inceleyebilir, sistemi kendi projelerine kolayca entegre edebilir ve ",{"_key":74,"_type":12,"marks":75,"text":76},"0ef1b5761444",[35],"açık kaynak koda katkıda bulunabilirsiniz:",[78],{"_key":34,"_type":79,"href":80},"link","https://www.worldometers.info/",{"_key":82,"_type":8,"children":83,"markDefs":110,"style":16},"02a610e3309d",[84,88,93,97,102,105],{"_key":85,"_type":12,"marks":86,"text":87},"642187f287c3",[],"👉",{"_key":89,"_type":12,"marks":90,"text":92},"4ca638f364f5",[91,35],"ece193ba4a17","Kazıma İşlemleri İçin Tıklayın",{"_key":94,"_type":12,"marks":95,"text":96},"c375f7ecab94",[],"\n👉",{"_key":98,"_type":12,"marks":99,"text":101},"88d443713b64",[100,35],"c034e55bc14c","Web Sitesi Kodları İçin Tıklayın",{"_key":103,"_type":12,"marks":104,"text":96},"7521ce454e68",[],{"_key":106,"_type":12,"marks":107,"text":109},"343d6fc4ca09",[108,35],"32eb37746e2a","Benimle İletişim İçin Tıklayın",[111,113,115],{"_key":91,"_type":79,"href":112},"http://fehu-zone/population-scraper-node",{"_key":100,"_type":79,"href":114},"https://github.com/fehu-zone/population-data-app",{"_key":108,"_type":79,"href":116},"https://ahmetkaradas.site/contact",{"_key":118,"_type":8,"children":119,"markDefs":124,"style":125},"1c0e9e07160a",[120],{"_key":121,"_type":12,"marks":122,"text":123},"c0ce7e0c5758",[],"Veri Kazıması İçin Kullandığım Teknolojiler",[],"h2",{"_key":127,"_type":8,"children":128,"markDefs":133,"style":134},"c564e36d1553",[129],{"_key":130,"_type":12,"marks":131,"text":132},"d00f31100b7e",[],"1. Veri Kazıma (Scraping)",[],"h3",{"_key":136,"_type":8,"children":137,"level":154,"listItem":155,"markDefs":156,"style":16},"01b8496bb38b",[138,142,146,150],{"_key":139,"_type":12,"marks":140,"text":141},"221a09754487",[35],"Puppeteer (+ Stealth Plugin)",{"_key":143,"_type":12,"marks":144,"text":145},"55dce4b9e1b7",[]," ile tarayıcı otomasyonu yapıldı. Anti-bot sistemlerini atlamak için ",{"_key":147,"_type":12,"marks":148,"text":149},"5c39da1af407",[35],"headless tarayıcı davranışı insan benzeri hale getirildi",{"_key":151,"_type":12,"marks":152,"text":153},"9af8d1cb963a",[],".",1,"bullet",[],{"_key":158,"_type":8,"children":159,"level":154,"listItem":155,"markDefs":168,"style":16},"a69e349ea29e",[160,164],{"_key":161,"_type":12,"marks":162,"text":163},"af1b0b3eb66a",[35],"Adblocker",{"_key":165,"_type":12,"marks":166,"text":167},"578a0dcb7e82",[]," eklentisi ile gereksiz istekler engellendi, performans artırıldı.",[],{"_key":170,"_type":8,"children":171,"level":154,"listItem":155,"markDefs":188,"style":16},"3ea963ec00cd",[172,176,180,184],{"_key":173,"_type":12,"marks":174,"text":175},"445f81f515a0",[35],"Cheerio",{"_key":177,"_type":12,"marks":178,"text":179},"3a0e0b07ba2b",[]," ile statik HTML’ler parse edildi, ",{"_key":181,"_type":12,"marks":182,"text":183},"2755cd681e46",[35],"Axios",{"_key":185,"_type":12,"marks":186,"text":187},"8b973cbab681",[]," ile API/HTTP istekleri atıldı.",[],{"_key":190,"_type":8,"children":191,"level":154,"listItem":155,"markDefs":200,"style":16},"0741903d0d20",[192,196],{"_key":193,"_type":12,"marks":194,"text":195},"170b3bfe4e26",[35],"Axios-retry",{"_key":197,"_type":12,"marks":198,"text":199},"78960d9740e0",[]," ile hatalı istekler otomatik yeniden denendi.",[],{"_key":202,"_type":8,"children":203,"level":154,"listItem":155,"markDefs":212,"style":16},"744227b89217",[204,208],{"_key":205,"_type":12,"marks":206,"text":207},"b9697aa4652e",[35],"Progress",{"_key":209,"_type":12,"marks":210,"text":211},"b933ad966205",[]," kütüphanesiyle terminalde işlem durumu real-time gösterildi.",[],{"_key":214,"_type":8,"children":215,"markDefs":220,"style":134},"ae3b56a7977f",[216],{"_key":217,"_type":12,"marks":218,"text":219},"b78409e1a28f",[],"2. Veri Depolama ve Sorgu",[],{"_key":222,"_type":8,"children":223,"level":154,"listItem":155,"markDefs":236,"style":16},"4e0fe0e32ca7",[224,228,232],{"_key":225,"_type":12,"marks":226,"text":227},"3b817a7f85df",[],"Toplanan veriler ",{"_key":229,"_type":12,"marks":230,"text":231},"97ce84ef947a",[35],"Elasticsearch",{"_key":233,"_type":12,"marks":234,"text":235},"54c9299a770e",[],"’e indexlendi.",[],{"_key":238,"_type":8,"children":239,"level":154,"listItem":155,"markDefs":248,"style":16},"31d99ac19e41",[240,244],{"_key":241,"_type":12,"marks":242,"text":243},"a3dae786a642",[35],"LRU Cache",{"_key":245,"_type":12,"marks":246,"text":247},"8ff8b69b2bd1",[]," ile sık erişilen veriler bellekte tutularak gereksiz istekler önlendi.",[],{"_key":250,"_type":8,"children":251,"markDefs":256,"style":134},"ce2d4104c107",[252],{"_key":253,"_type":12,"marks":254,"text":255},"0930011aded3",[],"3. Görselleştirme ve Dağıtım",[],{"_key":258,"_type":8,"children":259,"level":154,"listItem":155,"markDefs":267,"style":16},"09463068132f",[260,263],{"_key":261,"_type":12,"marks":262,"text":52},"ca6f3388f8be",[35],{"_key":264,"_type":12,"marks":265,"text":266},"09d3f6ec7589",[]," ile Elasticsearch’ten gelen veriler dashboard’a döküldü, analiz edildi.",[],{"_key":269,"_type":8,"children":270,"level":154,"listItem":155,"markDefs":291,"style":16},"f692449bd750",[271,275,279,283,287],{"_key":272,"_type":12,"marks":273,"text":274},"febb322162a0",[],"Tüm sistem ",{"_key":276,"_type":12,"marks":277,"text":278},"ce886ea8cda3",[35],"Docker",{"_key":280,"_type":12,"marks":281,"text":282},"d41d0b54a11a",[]," ile containerize edilip ",{"_key":284,"_type":12,"marks":285,"text":286},"aa75bd75b614",[35],"AWS EC2",{"_key":288,"_type":12,"marks":289,"text":290},"13d2ccfbcfe8",[],"’ye deploy edildi.",[],{"_key":293,"_type":8,"children":294,"markDefs":299,"style":16},"48022809b627",[295],{"_key":296,"_type":12,"marks":297,"text":298},"d82c733cc821",[35],"Neden Bu Teknolojileri tercih ettim?",[],{"_key":301,"_type":8,"children":302,"level":154,"listItem":155,"markDefs":318,"style":16},"77a36da6c906",[303,307,311,315],{"_key":304,"_type":12,"marks":305,"text":306},"599220e86a21",[35],"Puppeteer + Stealth",{"_key":308,"_type":12,"marks":309,"text":310},"666c9fe2690a",[],": Dinamik içeriklerde ",{"_key":312,"_type":12,"marks":313,"text":314},"ede0a7fc26a1",[35],"sürdürülebilir ve az kaynak kullanarak kazıma",{"_key":316,"_type":12,"marks":317,"text":153},"36020d902a9b",[],[],{"_key":320,"_type":8,"children":321,"level":154,"listItem":155,"markDefs":337,"style":16},"96a5d47ba1c5",[322,325,329,333],{"_key":323,"_type":12,"marks":324,"text":231},"fef879504f04",[35],{"_key":326,"_type":12,"marks":327,"text":328},"470c31e0398e",[],": Büyük veride ",{"_key":330,"_type":12,"marks":331,"text":332},"10b4697adcdf",[35],"full-text search",{"_key":334,"_type":12,"marks":335,"text":336},"09e5925d87fb",[]," ve hızlı sorgu.",[],{"_key":339,"_type":8,"children":340,"level":154,"listItem":155,"markDefs":349,"style":16},"a043443f36c0",[341,345],{"_key":342,"_type":12,"marks":343,"text":344},"31f402fa9056",[35],"Docker + EC2",{"_key":346,"_type":12,"marks":347,"text":348},"390b592de08e",[],": Kolay scaling(ölçekleme) ve düşük maliyet.",[],{"_key":351,"_type":8,"children":352,"markDefs":357,"style":125},"13f06cc018b0",[353],{"_key":354,"_type":12,"marks":355,"text":356},"c3f5cbdffd5d",[35],"Kazıma Sürecine Genel Bakış",[],{"_key":359,"_type":18,"asset":360},"f94980b1fadc",{"_ref":361,"_type":21},"image-50c8ffa52d75750fc4470d4e10e0d89f5dc5ed97-1600x1200-webp",{"_key":363,"_type":8,"children":364,"markDefs":370,"style":16},"cb4b190a56a8",[365],{"_key":366,"_type":12,"marks":367,"text":369},"727bf41d5761",[368],"em","Mimari ve Katmanlar\n",[],{"_key":372,"_type":8,"children":373,"markDefs":378,"style":125},"f2561969b0b8",[374],{"_key":375,"_type":12,"marks":376,"text":377},"120f23c77308",[],"1. Konfigürasyon: Ortam ve Yapılandırma",[],{"_key":380,"_type":8,"children":381,"markDefs":386,"style":16},"5fcedac8c88a",[382],{"_key":383,"_type":12,"marks":384,"text":385},"965e7aa2018b",[],"Projenin yapılandırma ayarlarını (Elasticsearch ve Kibana bağlantı bilgileri, User-Agent(tarayıcı taklidi), Worldometer URL’leri vb.) çevresel değişkenlerden (dotenv) alıyoruz. Böylece, farklı ortamlarda esnek konfigürasyon yönetimi sağlanıyor.",[],{"_key":388,"_type":389,"code":390,"language":391},"804ffdcd368e","code","import dotenv from \"dotenv\";\ndotenv.config();\n\nexport default {\n  ELASTICSEARCH_HOST: process.env.ELASTICSEARCH_HOST,\n  INDEX_NAME: process.env.INDEX_NAME,\n  ELASTIC_USERNAME: process.env.ELASTIC_USERNAME,\n  ELASTIC_PASSWORD: process.env.ELASTIC_PASSWORD,\n  REQUEST_HEADERS: {\n    \"User-Agent\":\n      process.env.USER_AGENT ||\n      \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36\",\n  },\n  COUNTRIES_URL:\n    process.env.COUNTRIES_URL ||\n    \"https://www.worldometers.info/world-population/population-by-country/?t=\" +\n      Date.now(),\n  WORLD_URL:\n    process.env.WORLD_URL || \"https://www.worldometers.info/world-population/\",\n};","javascript",{"_key":393,"_type":8,"children":394,"markDefs":399,"style":400},"cf4673917658",[395],{"_key":396,"_type":12,"marks":397,"text":398},"4929e3590eb8",[],"Bu yapı sayesinde; Elasticsearch host, indeks adı, kullanıcı adı, şifre gibi bilgileri .env dosyasından merkezi olarak yönetiyoruz. Güvenlik açısından gerek burada gerek repomda .env dosyamı paylaşamıyorum. Kendi kodunuza en uygun env dosyanızı rahatlıkla oluşturabilirsiniz",[],"blockquote",{"_key":402,"_type":8,"children":403,"markDefs":408,"style":125},"17e03006ca1f",[404],{"_key":405,"_type":12,"marks":406,"text":407},"7694b2552393",[],"2. Elasticsearch Bağlantısı ve İndeks Yönetimi",[],{"_key":410,"_type":8,"children":411,"markDefs":416,"style":16},"2242e1c0d00f",[412],{"_key":413,"_type":12,"marks":414,"text":415},"1d228871316b",[],"Elasticsearch’te verileri depolayabilmek için bir istemci oluşturuyoruz. Bu dosyada, Elasticsearch’ün indeksinin var olup olmadığını kontrol edip, yoksa dinamik olarak oluşturuyoruz. Ayrıca, “current” (güncel) snapshot’ları güncellemek için updateByQuery metodu kullanılıyor. Ek olarak, sürekli kazıma işlemi yapılan bu tarz projelerde sık karşılaşılan bir hata olan “Bu indeks zaten mevcut” hatasının önüne geçmiş oluyoruz.",[],{"_key":418,"_type":389,"code":419,"language":391},"03f39cdbfb25","import { Client } from \"@elastic/elasticsearch\";\nimport config from \"../config/index.js\";\n\nconst client = new Client({\n  node: config.ELASTICSEARCH_HOST,\n  auth: {\n    username: config.ELASTIC_USERNAME,\n    password: config.ELASTIC_PASSWORD,\n  },\n  tls: {\n    rejectUnauthorized: false,\n  },\n});\n\nexport const initIndex = async () => {\n  try {\n    const indexExists = await client.indices.exists({\n      index: config.INDEX_NAME,\n    });\n\n    if (!indexExists) {\n      await client.indices.create({\n        index: config.INDEX_NAME,\n        body: {\n          mappings: {\n            dynamic: \"strict\",\n            properties: {\n              country: {\n                type: \"text\",\n                fields: {\n                  keyword: {\n                    type: \"keyword\",\n                    ignore_above: 256,\n                  },\n                },\n              },\n              country_code: { type: \"keyword\" },\n              continent: { type: \"keyword\" },\n              current_population: { type: \"long\" },\n              yearly_change: { type: \"float\" },\n              net_change: { type: \"integer\" },\n              migrants: { type: \"integer\" },\n              med_age: { type: \"float\" },\n              population_growth: { type: \"float\" },\n              \"@timestamp\": { type: \"date\" },\n              is_current: { type: \"boolean\" },\n              type: { type: \"keyword\" },\n            },\n          },\n        },\n      });\n      console.log(`Index \"${config.INDEX_NAME}\" oluşturuldu.`);\n    } else {\n      console.log(`Index \"${config.INDEX_NAME}\" zaten mevcut.`);\n    }\n\n    return { created: !indexExists };\n  } catch (error) {\n    console.error(\"Index işlemleri sırasında hata:\", error.message);\n    throw error;\n  }\n};\n\nexport const updateCurrentSnapshot = async (timestamp) => {\n  try {\n    // Eski current verileri false yap\n    await client.updateByQuery({\n      index: config.INDEX_NAME,\n      conflicts: \"proceed\",\n      refresh: true,\n      body: {\n        script: {\n          source: \"ctx._source.is_current = false\",\n          lang: \"painless\",\n        },\n        query: {\n          term: { is_current: true },\n        },\n      },\n    });\n\n    // Yeni verileri güncel (current) yap\n    await client.updateByQuery({\n      index: config.INDEX_NAME,\n      conflicts: \"proceed\",\n      refresh: true,\n      body: {\n        script: {\n          source: \"ctx._source.is_current = true\",\n          lang: \"painless\",\n        },\n        query: {\n          bool: {\n            must: [\n              { term: { \"@timestamp\": timestamp } },\n              { terms: { type: [\"world\", \"country\"] } },\n            ],\n          },\n        },\n      },\n    });\n\n    console.log(`Güncel snapshot güncellendi: ${timestamp}`);\n  } catch (error) {\n    console.error(\"Snapshot güncelleme hatası:\", error.message);\n    throw error;\n  }\n};\n\nexport { client };",{"_key":421,"_type":8,"children":422,"markDefs":427,"style":400},"98291b549a11",[423],{"_key":424,"_type":12,"marks":425,"text":426},"01a38ada92c3",[],"Bu kod sayesinde, Elasticsearch ortamında dinamik veri modellemesi ve snapshot güncellemeleri yapılabilmekte.",[],{"_key":429,"_type":8,"children":430,"markDefs":435,"style":125},"580b6e2615d7",[431],{"_key":432,"_type":12,"marks":433,"text":434},"be79818173cb",[],"3. Ülke Verilerini Dinamik Olarak Kazıma",[],{"_key":437,"_type":8,"children":438,"markDefs":443,"style":16},"ecc12a72c03c",[439],{"_key":440,"_type":12,"marks":441,"text":442},"c0c78d5b87dd",[],"Puppeteer-extra (stealth özellikli) kullanılarak Worldometer’da ülke bazlı nüfus verileri çekiliyor. Sayfa tamamen yüklendikten sonra, tablo içerisindeki tüm satırlar için veri ayrıştırılması yapılmaktadır. Puppeteer-extra kullanmadan da gayet sağlıklı kazıma işlemi yaptım ancak fazladan önlem almak bize bir şey kaybettirmez.",[],{"_key":445,"_type":389,"code":446,"language":391},"8abddd570c68","import puppeteer from \"puppeteer-extra\";\nimport StealthPlugin from \"puppeteer-extra-plugin-stealth\";\nimport { parseNumber, cleanCountryName } from \"./utils.js\";\nimport config from \"../config/index.js\";\n\npuppeteer.use(StealthPlugin());\n\nexport const fetchCountryDataDynamic = async () => {\n  let browser;\n  let page;\n\n  try {\n    // Tarayıcı başlatma\n    browser = await puppeteer.launch({\n      headless: \"new\",\n      args: [\n        \"--no-sandbox\",\n        \"--disable-setuid-sandbox\",\n        \"--disable-dev-shm-usage\",\n        \"--disable-features=site-per-process\",\n        \"--lang=en-US\",\n        \"--window-size=1920,3000\",\n      ],\n    });\n\n    page = await browser.newPage();\n    await page.setViewport({ width: 1920, height: 3000 });\n\n    // Kullanıcı ajanı ayarı\n    await page.setUserAgent(config.REQUEST_HEADERS[\"User-Agent\"]);\n    await page.setJavaScriptEnabled(true);\n    await page.setDefaultNavigationTimeout(120000);\n\n    // Sayfa yükleme\n    console.log(\"Sayfa yükleniyor:\", config.COUNTRIES_URL);\n    await page.goto(config.COUNTRIES_URL, {\n      waitUntil: \"networkidle2\",\n      timeout: 120000,\n    });\n\n    // Tablonun doğru yüklenmesi için bekleme\n    await page.waitForFunction(\n      () => {\n        const potentialTables = Array.from(document.querySelectorAll(\"table\"));\n        return potentialTables.some((table) => {\n          const headers = Array.from(table.querySelectorAll(\"th\"));\n          return headers.some((th) => th.textContent.includes(\"Population\"));\n        });\n      },\n      { timeout: 45000 }\n    );\n\n    // Tablo verilerinin çekilmesi\n    const tableData = await page.evaluate(() => {\n      const tables = Array.from(document.querySelectorAll(\"table\"));\n      const targetTable = tables.find(\n        (table) =>\n          table.textContent.includes(\"Country\") &&\n          table.textContent.includes(\"Population\")\n      );\n\n      return Array.from(targetTable.querySelectorAll(\"tbody tr\")).map((row) => {\n        const cells = Array.from(row.querySelectorAll(\"td\"));\n        return cells.map((cell) =>\n          cell.textContent\n            .replace(/\\u00a0/g, \" \") // Özel boşluk karakterlerini temizle\n            .trim()\n        );\n      });\n    });\n\n    // Verilerin işlenmesi\n    const processedData = tableData\n      .map((row) => ({\n        rank: parseNumber(row[0]),\n        country: cleanCountryName(row[1]),\n        current_population: parseNumber(row[2]),\n        yearly_change: parseNumber(row[3], true),\n        net_change: parseNumber(row[4]),\n        migrants: parseNumber(row[7]),\n        med_age: parseNumber(row[9]),\n      }))\n      .filter((item) => item.rank > 0);\n\n    return processedData;\n  } catch (error) {\n    console.error(\"Son hata:\", error);\n    if (page) {\n      await page.screenshot({\n        path: `final-error-${Date.now()}.png`,\n        fullPage: true,\n      });\n    }\n    return null;\n  } finally {\n    if (browser) await browser.close();\n  }\n};\n",{"_key":448,"_type":8,"children":449,"markDefs":454,"style":125},"51dfe0e51c28",[450],{"_key":451,"_type":12,"marks":452,"text":453},"9d280a3e003a",[],"4. Statik HTML Üzerinden Dünya Verilerini Kazıma",[],{"_key":456,"_type":8,"children":457,"markDefs":462,"style":16},"f703ea2fafb0",[458],{"_key":459,"_type":12,"marks":460,"text":461},"69fa2678b7a8",[],"Bu yöntemde, Worldometer’ın ana sayfasından Axios ile HTML verisi çekilip, Cheerio kullanılarak ayrıştırma yapılıyor. Verilerin çekileceği alanlarda ilgili rel değerleri kullanılarak sayı değerleri elde ediliyor. Aşağıdaki kod parçası, statik yöntemle dünya nüfus verisini çekip ayrıştırmanın nasıl yapıldığını gösteriyor:",[],{"_key":464,"_type":389,"code":465,"language":391},"817a1b598ac9","import axios from \"axios\";\nimport * as cheerio from \"cheerio\";\nimport config from \"../config/index.js\";\nimport { parseNumber } from \"./utils.js\";\n\nexport const fetchWorldData = async () => {\n  try {\n    const { data } = await axios.get(config.WORLD_URL, {\n      headers: config.REQUEST_HEADERS,\n      timeout: 15000,\n    });\n\n    const $ = cheerio.load(data);\n\n    const extractValue = (relAttr) => {\n      const element = $(`span[rel=\"${relAttr}\"]`);\n      if (!element.length) return null;\n\n      return parseNumber(\n        element\n          .find(\".rts-nr-int\")\n          .toArray()\n          .map((el) => $(el).text().trim())\n          .join(\"\")\n      );\n    };\n\n    const result = {\n      current_population: extractValue(\"current_population\"),\n      births_today: extractValue(\"births_today\"),\n      // Günlük ölüm verisini \"dth1s_today\" anahtarıyla çekiyoruz.\n      dth1s_today: extractValue(\"dth1s_today\"),\n      population_growth: extractValue(\"absolute_growth\"),\n      \"@timestamp\": new Date().toISOString(),\n    };\n\n    if (Object.values(result).some((v) => v === null || Number.isNaN(v))) {\n      throw new Error(\"Eksik veya geçersiz dünya verileri\");\n    }\n\n    return result;\n  } catch (error) {\n    console.error(\"Dünya veri hatası:\", error.message);\n    return null;\n  }\n};",{"_key":467,"_type":8,"children":468,"markDefs":481,"style":400},"75367fd730be",[469,473,477],{"_key":470,"_type":12,"marks":471,"text":472},"236d584db683",[35,368],"Not:",{"_key":474,"_type":12,"marks":475,"text":476},"28ee0ceb5cde",[]," ",{"_key":478,"_type":12,"marks":479,"text":480},"84f82875e3d4",[368],"Bu yöntemde, HTML içeriği üzerinden doğrudan veri kazıyarak hızlı ve basit bir yapı elde edilmiş oluyor. Ancak bazı durumlarda, sayfa dinamik içerik sunduğu için eksik veri alınabilir. Buna karşı dinamik verileri çekmek için yazdığım kodları aşağıda paylaşacağım",[],{"_key":483,"_type":8,"children":484,"markDefs":489,"style":125},"fddfde23ab93",[485],{"_key":486,"_type":12,"marks":487,"text":488},"c795085f0ce0",[],"5. Dünya Verilerini Dinamik Kazıma",[],{"_key":491,"_type":8,"children":492,"markDefs":497,"style":16},"4858b221011a",[493],{"_key":494,"_type":12,"marks":495,"text":496},"14c4efc809d5",[],"Dinamik veri çekimi yönteminde ise Puppeteer kullanılarak tarayıcı başlatılıyor ve sayfada çalıştırılan JavaScript ile veriler elde ediliyor. Bu yöntem, statik HTML’de sunulmayan veya güncellenmeyen veriler için daha güvenilir sonuçlar verir.",[],{"_key":499,"_type":389,"code":500,"language":391},"0c7690daec8f","import puppeteer from \"puppeteer\";\nimport config from \"../config/index.js\";\nimport { parseNumber } from \"./utils.js\";\n\nexport const fetchWorldDataDynamic = async () => {\n  let browser;\n  try {\n    browser = await puppeteer.launch({\n      headless: \"new\",\n      ignoreHTTPSErrors: true,\n      args: [\n        \"--no-sandbox\",\n        \"--disable-setuid-sandbox\",\n        \"--disable-dev-shm-usage\",\n      ],\n    });\n\n    const page = await browser.newPage();\n    await page.setUserAgent(config.REQUEST_HEADERS[\"User-Agent\"]);\n    await page.setDefaultNavigationTimeout(30000);\n\n    await page.goto(config.WORLD_URL, {\n      waitUntil: \"networkidle2\",\n      timeout: 30000,\n    });\n\n    const result = await page.evaluate(() => {\n      const getValue = (rel) => {\n        const el = document.querySelector(`[rel='${rel}']`);\n        return el\n          ? Array.from(el.querySelectorAll(\".rts-nr-int\"))\n              .map((e) => e.textContent.trim())\n              .join(\"\")\n          : \"\";\n      };\n\n      return {\n        current_population: getValue(\"current_population\"),\n        births_today: getValue(\"births_today\"),\n        // Anahtar adını, site ile uyumlu olacak şekilde \"dth1s_today\" olarak ayarlıyoruz.\n        dth1s_today: getValue(\"dth1s_today\"),\n        population_growth: getValue(\"absolute_growth\"),\n        \"@timestamp\": new Date().toISOString(),\n      };\n    });\n\n    return {\n      current_population: parseNumber(result.current_population),\n      births_today: parseNumber(result.births_today),\n      dth1s_today: parseNumber(result.dth1s_today),\n      population_growth: parseNumber(result.population_growth),\n      \"@timestamp\": result[\"@timestamp\"],\n    };\n  } catch (error) {\n    console.error(\"Dünya veri hatası:\", error);\n    return null;\n  } finally {\n    if (browser) await browser.close();\n  }\n};\n",{"_key":502,"_type":8,"children":503,"markDefs":514,"style":400},"0339805e0a3e",[504,507,510],{"_key":505,"_type":12,"marks":506,"text":472},"d0cca3e1c308",[35,368],{"_key":508,"_type":12,"marks":509,"text":476},"72c6db28e38c",[],{"_key":511,"_type":12,"marks":512,"text":513},"7f2170ca8061",[368],"Dinamik kazıma yöntemi, özellikle JavaScript tarafından oluşturulan içerikleri çekmekte çok daha etkin olduğu için, sayfa güncellemelerine daha iyi ayak uydurur ve bu sayede daha güvenilir sonuçlar elde ederiz. Fakat bu yöntem statik yönteme göre daha uzun sürer ve daha çok kaynak tüketir.",[],{"_key":516,"_type":8,"children":517,"markDefs":522,"style":134},"0fe9b324e8c2",[518],{"_key":519,"_type":12,"marks":520,"text":521},"a4775e3ff686",[],"Fallback Mantığının Uygulanması",[],{"_key":524,"_type":8,"children":525,"markDefs":530,"style":16},"50be98a47877",[526],{"_key":527,"_type":12,"marks":528,"text":529},"f9c557e26456",[],"Projeme ilk başladığımda dünya bazlı nüfus verilerini çekerken sık karşılaştığım hatalardan birisi ise “null dönen” verilerdi. Statik olarak verileri almak istedim ancak Worldometerin yapısından dolayı bu her zaman mümkün olmuyordu. Puppeteer kullandım ve dinamik kazıma yapısını koduma entegre ettim. Fallback mekanizmasını kurdum",[],{"_key":532,"_type":8,"children":533,"markDefs":538,"style":539},"7397d74d97c5",[534],{"_key":535,"_type":12,"marks":536,"text":537},"55118da7480b",[],"Geliştirdiğim fallback mekanizması sayesinde, eğer dinamik veri çekiminde bir hata oluşursa, otomatik olarak statik veri çekme yöntemi devreye girecek, böylece verilerin sürekliliği sağlanacaktır.",[],"h4",{"_key":541,"_type":8,"children":542,"markDefs":547,"style":125},"8de2548731d2",[543],{"_key":544,"_type":12,"marks":545,"text":546},"b7aedf84d8eb",[],"6. Yardımcı Fonksiyonlar",[],{"_key":549,"_type":8,"children":550,"markDefs":555,"style":16},"c4f0fbf564a3",[551],{"_key":552,"_type":12,"marks":553,"text":554},"01b02b835200",[],"İki temel yardımcı fonksiyon — sayısal verileri parse etmek ve ülke isimlerini temizlemek — aşağıdaki kodlarda yer almaktadır.. Ek olarak, toplu indexleme (bulkIndexCountries) fonksiyonu da Elasticsearch işlemleri için bulunuyor.",[],{"_key":557,"_type":389,"code":558,"language":391},"4fa6cfc33336","import { client } from \"../elastic/client.js\";\nimport config from \"../config/index.js\";\n\nexport const parseNumber = (str, isPercentage = false) => {\n  if ([null, undefined, \"\"].includes(str)) return 0; // Null değerler için 0\n\n  const cleaned = String(str)\n    .replace(/[^\\d.-]/g, \"\")\n    .replace(/^\\-/g, \"-\");\n\n  const number = parseFloat(cleaned);\n  return Number.isNaN(number) ? 0 : number; // NaN durumunda 0\n};\n\nexport const cleanCountryName = (name) => {\n  const COUNTRY_NAME_MAPPING = {\n    /* ... */\n  };\n\n  return (\n    COUNTRY_NAME_MAPPING[name] ||\n    name\n      .replace(/\\[.*?\\]/g, \"\")\n      .replace(/\\(.*?\\)/g, \"\")\n      .trim()\n  );\n};\n\nexport const bulkIndexCountries = async (countries) => {\n  try {\n    // EKSİK PARANTEZ DÜZELTİLDİ\n    if (!Array.isArray(countries)) {\n      throw new Error(\"Geçersiz ülke veri formatı\");\n    }\n\n    const { body: updateResponse } = await client.updateByQuery({\n      /* ... */\n    });\n\n    const body = countries.flatMap((country) => [\n      /* ... */\n    ]);\n\n    const { body: bulkResponse } = await client.bulk({\n      /* ... */\n    });\n\n    return {\n      /* ... */\n    };\n  } catch (error) {\n    return {\n      /* ... */\n    };\n  }\n};",{"_key":560,"_type":8,"children":561,"markDefs":566,"style":125},"05330bc0d429",[562],{"_key":563,"_type":12,"marks":564,"text":565},"42407f3555f5",[],"7. Ana İşlem Akışı: main.js",[],{"_key":568,"_type":8,"children":569,"markDefs":574,"style":16},"142eca9ca782",[570],{"_key":571,"_type":12,"marks":572,"text":573},"ec31433198ad",[],"Bu ana dosya, projenin merkez üssü diyebiliriz. Veri kazıma işlemlerini koordine etmekle kalmaz; aynı zamanda verilerin doğruluğunu denetler, Elasticsearch’e gönderimini sağlar, snapshot’ları günceller ve sistem kaynaklarını izleyerek hangi kazıma yönteminin tercih edilmesi gerektiğine karar verir. Yani, hem teknik akışı hem de verimliliği yöneten bir kontrol paneli gibi çalışır.",[],{"_key":576,"_type":389,"code":577,"language":391},"d17b2d42b6d8","process.env.NODE_TLS_REJECT_UNAUTHORIZED = \"0\";\nimport dotenv from \"dotenv\";\ndotenv.config();\n\n// .env dosyasından bilgileri alıyoruz.\n// Eğer ELASTICSEARCH_HOST tanımlı değilse, varsayılan olarak yerel sunucuyu kullan.\nconst ELASTICSEARCH_HOST =\n  process.env.ELASTICSEARCH_HOST || \"http://localhost:9200/\";\n\nimport { fetchCountryDataDynamic } from \"./scraper/countryDataDynamic.js\";\nimport { fetchWorldDataDynamic } from \"./scraper/worldDataDynamic.js\";\nimport { initIndex, client } from \"./elastic/client.js\";\nimport ProgressBar from \"progress\";\nimport { updateCurrentSnapshot } from \"./elastic/client.js\";\n\n// Gelişmiş Loglama Sistemi\nconst logger = {\n  info: (message) =>\n    console.log(\n      `\\x1b[36mℹ️ [${new Date().toLocaleTimeString()}] ${message}\\x1b[0m`\n    ),\n  success: (message) =>\n    console.log(\n      `\\x1b[32m✅ [${new Date().toLocaleTimeString()}] ${message}\\x1b[0m`\n    ),\n  error: (message) =>\n    console.log(\n      `\\x1b[31m❌ [${new Date().toLocaleTimeString()}] ${message}\\x1b[0m`\n    ),\n  warn: (message) =>\n    console.log(\n      `\\x1b[33m⚠️ [${new Date().toLocaleTimeString()}] ${message}\\x1b[0m`\n    ),\n};\n\n// Geliştirilmiş Veri Doğrulama\nconst validateData = (worldData, countryData) => {\n  const warnings = [];\n  const errors = [];\n  const EXPECTED_COUNTRIES = 235;\n\n  // Dünya verisi kontrolleri\n  if (!worldData?.current_population) {\n    errors.push(\"Dünya nüfus verisi eksik\");\n  }\n\n  // Ülke verisi kontrolleri\n  if (!countryData || countryData.length === 0) {\n    errors.push(\"Hiç ülke verisi alınamadı\");\n    return { isValid: false, errors, warnings };\n  }\n\n  const totalCountries = countryData.length;\n  const validCountries = countryData.filter(\n    (c) =>\n      c.current_population > 0 && !isNaN(c.yearly_change) && !isNaN(c.med_age)\n  ).length;\n\n  // Uyarılar\n  if (totalCountries \u003C EXPECTED_COUNTRIES) {\n    warnings.push(`Eksik ülke: ${EXPECTED_COUNTRIES - totalCountries}`);\n  }\n\n  const criticalMissing = [\"China\", \"India\", \"United States\"].filter(\n    (c) => !countryData.some((d) => d.country === c)\n  );\n\n  if (criticalMissing.length > 0) {\n    warnings.push(`Eksik kritik ülkeler: ${criticalMissing.join(\", \")}`);\n  }\n\n  if (totalCountries - validCountries > 0) {\n    warnings.push(\n      `Geçersiz veri içeren ülkeler: ${totalCountries - validCountries}`\n    );\n  }\n\n  // Hatalar\n  if (validCountries === 0) {\n    errors.push(\"Hiç geçerli ülke verisi yok\");\n  }\n\n  return {\n    isValid: errors.length === 0,\n    errors,\n    warnings,\n  };\n};\n\n// Enerji Tüketimi Ölçüm Fonksiyonu\nconst measureEnergyConsumption = async (fn, label = \"İşlem\") => {\n  const startTime = process.hrtime();\n  const startCpuUsage = process.cpuUsage();\n\n  try {\n    const result = await fn();\n\n    const elapsedTime = process.hrtime(startTime);\n    const elapsedCpu = process.cpuUsage(startCpuUsage);\n    const cpuSeconds = (elapsedCpu.user + elapsedCpu.system) / 1e6;\n    const wallSeconds = elapsedTime[0] + elapsedTime[1] / 1e9;\n    const cpuWattage = 50;\n    const estimatedEnergyJoules = cpuSeconds * cpuWattage;\n\n    logger.info(`\\n== ${label} Enerji Tüketim Raporu ==`);\n    logger.info(`Duvar saati süresi: ${wallSeconds.toFixed(3)} s`);\n    logger.info(`CPU kullanım süresi: ${cpuSeconds.toFixed(3)} s`);\n    logger.info(\n      `Tahmini enerji tüketimi: ${estimatedEnergyJoules.toFixed(\n        2\n      )} J (ortalama ${cpuWattage}W kabul edilerek)`\n    );\n    return result;\n  } catch (error) {\n    throw error;\n  }\n};\n\n// Ana İşlem Akışı\nconst processData = async () => {\n  try {\n    logger.info(\"Scraping süreci başlatılıyor...\");\n\n    // Elasticsearch hazırlığı\n    await initIndex();\n\n    // 1. Dünya verilerini çek\n    logger.info(\"════════════ DÜNYA VERİLERİ ÇEKİLİYOR ════════════\");\n    const worldData = await fetchWithProgress(\n      fetchWorldDataDynamic,\n      \"🌍 Dünya verisi\",\n      15,\n      120000\n    );\n\n    // 2. Bekleme süresi\n    logger.info(\"Dünya verisi alındıktan sonra 20 saniye bekleniyor...\");\n    await delay(20000);\n\n    // 3. Ülke verilerini çek\n    logger.info(\"════════════ ÜLKE VERİLERİ ÇEKİLİYOR ════════════\");\n    const countryData = await fetchWithProgress(\n      fetchCountryDataDynamic,\n      \"🌐 Ülke verisi\",\n      30,\n      240000\n    );\n\n    // Sonuçları işle\n    const results = { world: worldData, country: countryData };\n    logResults(results);\n\n    // Validasyon\n    const validation = validateData(results.world, results.country);\n    handleValidation(validation);\n\n    // Elasticsearch'e gönder\n    const { successCount, errorCount } = await sendToElastic(results);\n\n    logger.success(`Başarıyla kaydedildi: ${successCount} kayıt`);\n    if (errorCount > 0) {\n      logger.warn(`Başarısız kayıtlar: ${errorCount}`);\n    }\n\n    // Snapshot güncelleme\n    await updateCurrentSnapshot(new Date().toISOString());\n  } catch (error) {\n    logger.error(`Kritik Hata: ${error.message}`);\n    logger.info(\"5 dakika sonra yeniden denenecek...\");\n    setTimeout(() => processDataWithEnergy(), 300000);\n  }\n};\n\n// Enerji ölçümü dahil ana işlem çağrısı\nconst processDataWithEnergy = async () => {\n  await measureEnergyConsumption(processData, \"processData\");\n};\n\n// Yardımcı Fonksiyonlar\nconst fetchWithProgress = async (fetchFn, label, total, timeout) => {\n  const bar = new ProgressBar(`${label} [:bar] :percent :etas`, {\n    complete: \"=\",\n    incomplete: \" \",\n    width: 30,\n    total,\n  });\n\n  const timer = setInterval(() => bar.tick(), 1000);\n\n  try {\n    const result = await Promise.race([\n      fetchFn(),\n      new Promise((_, reject) =>\n        setTimeout(() => reject(new Error(`${label} zaman aşımı`)), timeout)\n      ),\n    ]);\n\n    clearInterval(timer);\n    bar.update(1);\n    return result;\n  } catch (error) {\n    clearInterval(timer);\n    throw error;\n  }\n};\n\nconst logResults = (results) => {\n  logger.info(\"════════════ DÜNYA VERİLERİ ════════════\");\n  if (results.world) {\n    logger.info(\n      `🌍 Nüfus: ${results.world.current_population?.toLocaleString()}`\n    );\n    logger.info(\n      `📈 Günlük Büyüme: ${results.world.population_growth?.toLocaleString()}`\n    );\n    logger.info(`⏳ Zaman Damgası: ${results.world[\"@timestamp\"]}`);\n  } else {\n    logger.error(\"Dünya verisi yok\");\n  }\n\n  logger.info(\"════════════ ÜLKE VERİLERİ ════════════\");\n  if (results.country?.length > 0) {\n    logger.info(`✅ Toplam Ülke: ${results.country.length}`);\n    logger.info(\n      `🏆 İlk 3 Ülke: ${results.country\n        .slice(0, 3)\n        .map((c) => c.country)\n        .join(\", \")}`\n    );\n    logger.info(`📊 Ortalama Yaş: ${calculateAverageAge(results.country)}`);\n  } else {\n    logger.error(\"Ülke verisi yok\");\n  }\n};\n\nconst handleValidation = ({ isValid, errors, warnings }) => {\n  if (!isValid) {\n    logger.error(\"Validasyon Hataları:\");\n    errors.forEach((e) => logger.error(`❌ ${e}`));\n    throw new Error(\"Kritik validasyon hataları\");\n  }\n\n  if (warnings.length > 0) {\n    logger.warn(\"Validasyon Uyarıları:\");\n    warnings.forEach((w) => logger.warn(`⚠️  ${w}`));\n  }\n};\n\nconst sendToElastic = async ({ world, country }) => {\n  const body = [];\n\n  try {\n    // Dünya verisini ekle\n    if (world) {\n      body.push(\n        { index: { _index: process.env.INDEX_NAME } },\n        {\n          ...world,\n          type: \"world\",\n          is_current: true,\n          \"@timestamp\": new Date().toISOString(),\n        }\n      );\n    }\n\n    // Ülke verilerini ekle\n    if (country?.length > 0) {\n      country.forEach((c) => {\n        body.push(\n          { index: { _index: process.env.INDEX_NAME } },\n          {\n            ...c,\n            type: \"country\",\n            is_current: true,\n            \"@timestamp\": new Date().toISOString(),\n            current_population: c.current_population || 0,\n            yearly_change: c.yearly_change || 0,\n            net_change: c.net_change || 0,\n            migrants: c.migrants || 0,\n            med_age: c.med_age || 0,\n          }\n        );\n      });\n    }\n\n    if (body.length === 0) {\n      logger.warn(\"Gönderilecek veri yok\");\n      return { successCount: 0, errorCount: 0 };\n    }\n\n    const { body: response } = await client.bulk({\n      refresh: \"wait_for\",\n      body,\n    });\n\n    let successCount = 0;\n    let errorCount = 0;\n    const errors = [];\n\n    if (response?.items) {\n      response.items.forEach((item, index) => {\n        if (item.index.error) {\n          errorCount++;\n          errors.push({\n            document: body[index * 2 + 1],\n            reason: item.index.error.reason,\n          });\n        } else {\n          successCount++;\n        }\n      });\n    }\n\n    if (errorCount > 0) {\n      logger.error(`İlk 3 hata detayı:`);\n      errors.slice(0, 3).forEach((err, i) => {\n        logger.error(`${i + 1}. Hata: ${err.reason}`);\n        logger.error(`Belge: ${JSON.stringify(err.document)}`);\n      });\n    }\n\n    return { successCount, errorCount };\n  } catch (error) {\n    logger.error(\"Elasticsearch hatası:\");\n    if (error.meta) {\n      logger.error(`Hata detayı: ${JSON.stringify(error.meta.body.error)}`);\n    } else {\n      logger.error(error.stack);\n    }\n    throw error;\n  }\n};\n\nconst calculateAverageAge = (countries) => {\n  const validAges = countries\n    .map((c) => c.med_age)\n    .filter((age) => age > 0 && age \u003C 100);\n\n  return validAges.length > 0\n    ? (validAges.reduce((sum, age) => sum + age, 0) / validAges.length).toFixed(\n        1\n      )\n    : \"N/A\";\n};\n\nconst delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));\n\nconsole.clear();\nprocessDataWithEnergy();\nsetInterval(processDataWithEnergy, 1800000); // Her 30 dakikada bir çalıştır.",{"_key":579,"_type":8,"children":580,"markDefs":585,"style":400},"ff30a5e55f69",[581],{"_key":582,"_type":12,"marks":583,"text":584},"5197ffa99bb7",[368],"Kodlarda olası bir sorun yaşamanıza karşılık soruna daha hızlı müdahale edebilmeniz için hata ile alakalı size ekran görüntüsü gelecektir.",[],{"_key":587,"_type":8,"children":588,"markDefs":593,"style":16},"236545045018",[589],{"_key":590,"_type":12,"marks":591,"text":592},"501236d1a921",[],"",[],{"_key":595,"_type":8,"children":596,"markDefs":601,"style":125},"112f35d604b8",[597],{"_key":598,"_type":12,"marks":599,"text":600},"d38f1fa6d848",[35],"Projenizi İnsanlara Sunmanın En İyi Yolu: Web Sitesi",[],{"_key":603,"_type":8,"children":604,"markDefs":629,"style":16},"6030670a8898",[605,609,613,617,621,625],{"_key":606,"_type":12,"marks":607,"text":608},"74641e4f2f9d",[35],"Kibana’da",{"_key":610,"_type":12,"marks":611,"text":612},"f382a47e4143",[]," oluşturduğum gösterge panoları gerçekten hoşuma gitmişti. Ancak yerelde (local) çalışırken, bu etkileyici görselleştirmeleri insanlarla dilediğim gibi paylaşamıyordum. Bu nedenle işe ",{"_key":614,"_type":12,"marks":615,"text":616},"78942b3c82d8",[35],"Figma’da",{"_key":618,"_type":12,"marks":619,"text":620},"7ed53b23041c",[]," bir web sitesi tasarlayarak başladım. Tasarımın ardından ",{"_key":622,"_type":12,"marks":623,"text":624},"0297365f6cd6",[35],"Vue.js",{"_key":626,"_type":12,"marks":627,"text":628},"f609532da71c",[]," ile bu tasarımı hayata geçirdim ve siteyi tamamen etkileşimli bir hale getirdim. Projeyi yavaş yavaş canlıya alma fikri de tam olarak burada tomurcuklandı.",[],{"_key":631,"_type":8,"children":632,"markDefs":645,"style":16},"9ab36a0b8f1f",[633,637,641],{"_key":634,"_type":12,"marks":635,"text":636},"f1f605055baf",[],"Web sitem ve veri kazıma işlemleri, ",{"_key":638,"_type":12,"marks":639,"text":640},"be0f6fbb0c7b",[35],"AWS",{"_key":642,"_type":12,"marks":643,"text":644},"43f580ff9d21",[]," üzerinde çalışan bir sunucu sayesinde kesintisiz şekilde çalışıyor. Yukarıda bahsettiğim “Kazıma Sürecine Genel Bakış” başlığı ve aşağıya koyacağım web sitesi-kibana etkileşimi diagramını tekrar inceleyerek süreci daha iyi anlayabilirsiniz.",[],{"_key":647,"_type":8,"children":648,"markDefs":653,"style":16},"2f8011200266",[649],{"_key":650,"_type":12,"marks":651,"text":652},"b21651c7f239",[],"Veri kaynağından kullanıcıya kadar olan akışı ve Vue ile Kibana’nın nasıl bir arada çalıştığını sade bir şekilde gözler önüne seriyor. 👇",[],{"_key":655,"_type":8,"children":656,"markDefs":660,"style":16},"25e7498f9939",[657],{"_key":658,"_type":12,"marks":659,"text":592},"8e537c92316f",[],[],{"_key":662,"_type":18,"asset":663},"a52bcaef215b",{"_ref":664,"_type":21},"image-5f4f4c6bf8be17dad679a70864b8c2f99408e4ff-2400x1350-webp",{"_key":666,"_type":8,"children":667,"markDefs":672,"style":400},"55f3ed246fad",[668],{"_key":669,"_type":12,"marks":670,"text":671},"e27eaf4bd65d",[],"Web sitesinin yayında olduğu ve kazıma işlemlerinin yapıldığı yer AWS sunucusudur. “Kazıma Sürecine Genel Bakış” başlığına giderek web sitesi ve Kibana arasındaki etkileşimi daha iyi anlayabilirsiniz.",[],{"_key":674,"_type":8,"children":675,"markDefs":679,"style":16},"4d6dbd9bcb47",[676],{"_key":677,"_type":12,"marks":678,"text":592},"6767a90973c2",[],[],{"_key":681,"_type":8,"children":682,"markDefs":687,"style":125},"45dfdd2ad2cc",[683],{"_key":684,"_type":12,"marks":685,"text":686},"a0c782bafe4d",[],"Web Site Entegrasyonu ve Teknolojiler",[],{"_key":689,"_type":8,"children":690,"markDefs":695,"style":16},"6c9ebac7d3cf",[691],{"_key":692,"_type":12,"marks":693,"text":694},"a2f40362d591",[],"Web sitemi oluştururken:",[],{"_key":697,"_type":8,"children":698,"level":154,"listItem":155,"markDefs":715,"style":16},"9597bb4dc17a",[699,703,707,711],{"_key":700,"_type":12,"marks":701,"text":702},"b44efad09fca",[35],"Vue 3",{"_key":704,"_type":12,"marks":705,"text":706},"e53e9dfcfc20",[]," + ",{"_key":708,"_type":12,"marks":709,"text":710},"7e581e94bc43",[35],"Vite",{"_key":712,"_type":12,"marks":713,"text":714},"507abbe80d77",[]," altyapısını tercih ettim.",[],{"_key":717,"_type":8,"children":718,"level":154,"listItem":155,"markDefs":739,"style":16},"cc7e31f31226",[719,723,727,731,735],{"_key":720,"_type":12,"marks":721,"text":722},"49183345d475",[],"Birim ve entegrasyon testlerini ",{"_key":724,"_type":12,"marks":725,"text":726},"dbec88fe602e",[35],"Vitest",{"_key":728,"_type":12,"marks":729,"text":730},"d768ea38b519",[]," ve ",{"_key":732,"_type":12,"marks":733,"text":734},"271891cfcbb3",[35],"Cypress",{"_key":736,"_type":12,"marks":737,"text":738},"4b96e4caf90f",[]," ile yazdım.",[],{"_key":741,"_type":8,"children":742,"level":154,"listItem":155,"markDefs":747,"style":16},"ded0614e2266",[743],{"_key":744,"_type":12,"marks":745,"text":746},"e77cad98ef06",[],"Temel SEO ayarlarını (meta başlık, açıklama, Open Graph) sağladım.",[],{"_key":749,"_type":8,"children":750,"level":154,"listItem":155,"markDefs":767,"style":16},"a25f8e6e9e4a",[751,755,759,763],{"_key":752,"_type":12,"marks":753,"text":754},"9a7bfc0f2a9e",[35],"vue-i18n",{"_key":756,"_type":12,"marks":757,"text":758},"cc291501617f",[]," kullanarak ",{"_key":760,"_type":12,"marks":761,"text":762},"726b0f1eb066",[35],"9 dil desteği",{"_key":764,"_type":12,"marks":765,"text":766},"1a594e682cfd",[]," ekledim ve her dil için özel olarak mobile/desktop responsive tasarımlar yaptım.",[],{"_key":769,"_type":8,"children":770,"level":154,"listItem":155,"markDefs":783,"style":16},"c141dfd9c519",[771,775,779],{"_key":772,"_type":12,"marks":773,"text":774},"5191300c364e",[],"Stil ve responsive düzenlemeleri için sadece ",{"_key":776,"_type":12,"marks":777,"text":778},"4229f5285119",[35],"saf CSS",{"_key":780,"_type":12,"marks":781,"text":782},"38e3a71f9e99",[]," kullandım; ekstra kütüphane yüklemedim.",[],{"_key":785,"_type":8,"children":786,"level":154,"listItem":155,"markDefs":791,"style":16},"446a5af18a24",[787],{"_key":788,"_type":12,"marks":789,"text":790},"73e7ac960a7a",[],"Dünya ve ülke sayfalarını, Elasticsearch + Kibana’dan gelen iframe’ler aracılığıyla doğrudan gömme (embed) yöntemiyle oluşturdum.",[],{"_key":793,"_type":8,"children":794,"level":154,"listItem":155,"markDefs":806,"style":16},"1e47e777f689",[795,799,802],{"_key":796,"_type":12,"marks":797,"text":798},"ee9ade79c9ca",[],"Web sitemi ",{"_key":800,"_type":12,"marks":801,"text":640},"0b857cafdcc2",[35],{"_key":803,"_type":12,"marks":804,"text":805},"137edfbef54a",[]," üzerinden canlıya aldım.",[],{"_key":808,"_type":8,"children":809,"markDefs":814,"style":125},"65a4ca12c7b6",[810],{"_key":811,"_type":12,"marks":812,"text":813},"950281cbf825",[],"Kibana ve Web Sitesi İletişimi",[],{"_key":816,"_type":8,"children":817,"level":154,"listItem":155,"markDefs":822,"style":16},"ef991319e736",[818],{"_key":819,"_type":12,"marks":820,"text":821},"e632871be9cc",[],"Kibana’ya erişin",[],{"_key":824,"_type":8,"children":825,"level":154,"listItem":155,"markDefs":830,"style":16},"670ba94a01b0",[826],{"_key":827,"_type":12,"marks":828,"text":829},"cbdfbf928064",[],"Dashboard tasarımlarınızı yapın",[],{"_key":832,"_type":8,"children":833,"level":154,"listItem":155,"markDefs":846,"style":16},"48b7854585fa",[834,838,842],{"_key":835,"_type":12,"marks":836,"text":837},"28fba4c6a120",[],"Tasarımınız bittikten sonra “share” seçeneğine tıklayarak istediğiniz bağlantı türünü seçin(ben projemde ",{"_key":839,"_type":12,"marks":840,"text":841},"0a8255d8f9c2",[35],"“embed code”",{"_key":843,"_type":12,"marks":844,"text":845},"3ee01568b202",[]," seçeneğini tercih ettim)",[],{"_key":848,"_type":8,"children":849,"markDefs":854,"style":16},"0b2e67166d8a",[850],{"_key":851,"_type":12,"marks":852,"text":853},"39ee05ff98a8",[],"Aşağıda Elasticsearch’ün örnek verdiği iframe bulunmaktadır. Test edebilirsiniz.",[],{"_key":856,"_type":8,"children":857,"markDefs":861,"style":16},"e04148ed792c",[858],{"_key":859,"_type":12,"marks":860,"text":592},"4ed65d2e1a03",[],[],{"_key":863,"_type":389,"code":864,"language":865},"4ab1a76f7a80","\u003Ciframe src=\"https://my-deployment:9243/app/dashboards#/view/7adfa750-4c81-11e8-b3d7-01146121b73d?embed=true&_g=(refreshInterval%3A(pause%3A!t%2Cvalue%3A0)%2Ctime%3A(from%3Anow-1y%2Fd%2Cto%3Anow))&show-top-menu=true&show-query-input=true&show-time-filter=true\" height=\"600\" width=\"800\">\u003C/iframe>","html",{"_key":867,"_type":8,"children":868,"markDefs":890,"style":16},"9358fa5920a0",[869,873,877,881,886],{"_key":870,"_type":12,"marks":871,"text":872},"8aee22ef7cc4",[],"\n",{"_key":874,"_type":12,"marks":875,"text":876},"71447fea0dda",[35],"NOT!!!",{"_key":878,"_type":12,"marks":879,"text":880},"ff6902ac06d7",[]," İframe’i nasıl göstereceğinize dair detaylar sizin gereksinimlerinize göre değişiklik göstermektedir. Ek ayarlar vb. Şeyler için lütfen ",{"_key":882,"_type":12,"marks":883,"text":885},"625c94c39634",[884],"dc4663c52ce7","bu makaleyi",{"_key":887,"_type":12,"marks":888,"text":889},"e195e2ce4614",[]," okuyun.",[891],{"_key":884,"_type":79,"href":892},"https://www.elastic.co/blog/how-to-embed-kibana-dashboards",{"_key":894,"_type":8,"children":895,"markDefs":899,"style":16},"15ff13660883",[896],{"_key":897,"_type":12,"marks":898,"text":592},"992013dd03d9",[],[],{"_key":901,"_type":18,"asset":902},"b06d629b5903",{"_ref":903,"_type":21},"image-554f41cf59221b047c48c214624ba04ab399a3a1-1600x1200-webp",{"_key":905,"_type":8,"children":906,"markDefs":910,"style":16},"e0b42c15ef43",[907],{"_key":908,"_type":12,"marks":909,"text":592},"7c549358a1f9",[],[],{"_key":912,"_type":18,"asset":913},"5faf0b1e98d5",{"_ref":914,"_type":21},"image-a387c43e5991429ed108388fe0d0c02cb6790f9d-1600x1200-webp",{"_key":916,"_type":8,"children":917,"markDefs":922,"style":400},"5b748bc640b5",[918],{"_key":919,"_type":12,"marks":920,"text":921},"324958f122d5",[],"Dünya Bazlı Verilerin Kibana Dashboard Üzerinden Görselleştirilmesi",[],{"_key":924,"_type":8,"children":925,"markDefs":929,"style":16},"fea6a44234ad",[926],{"_key":927,"_type":12,"marks":928,"text":592},"cd27d32cf563",[],[],{"_key":931,"_type":8,"children":932,"markDefs":937,"style":125},"3042354443be",[933],{"_key":934,"_type":12,"marks":935,"text":936},"aea70521363c",[],"Localde Çalışmak Güzel Ancak Projeyi Canlıya Almalıyız",[],{"_key":939,"_type":8,"children":940,"markDefs":953,"style":16},"1fbbe6084f5e",[941,945,949],{"_key":942,"_type":12,"marks":943,"text":944},"f706f0903593",[],"Projeyi canlıya almak için AWS’in Free Tier kapsamındaki ",{"_key":946,"_type":12,"marks":947,"text":948},"cd1abf731f25",[35],"t3.medium",{"_key":950,"_type":12,"marks":951,"text":952},"54ae186e4122",[]," EC2 kullandım.",[],{"_key":955,"_type":8,"children":956,"markDefs":961,"style":134},"2f4788bb3cff",[957],{"_key":958,"_type":12,"marks":959,"text":960},"77d568bdac8d",[35],"1. EC2 Kurulumu",[],{"_key":963,"_type":8,"children":964,"level":154,"listItem":155,"markDefs":977,"style":16},"e1769a9a456a",[965,969,973],{"_key":966,"_type":12,"marks":967,"text":968},"32292c862766",[],"AWS Console’a giriş yaparak ",{"_key":970,"_type":12,"marks":971,"text":972},"847f819e6ee7",[35],"EC2 → Launch Instance",{"_key":974,"_type":12,"marks":975,"text":976},"35b362f64631",[]," seçeneğini seçin.",[],{"_key":979,"_type":8,"children":980,"level":154,"listItem":155,"markDefs":992,"style":16},"fac5f91c3c43",[981,985,988],{"_key":982,"_type":12,"marks":983,"text":984},"7d2fc74fe817",[],"Instance türü olarak, Free Tier kapsamında yer alan ",{"_key":986,"_type":12,"marks":987,"text":948},"42bbde37a495",[35],{"_key":989,"_type":12,"marks":990,"text":991},"23922d9bbac2",[],"’ı tercih ettim (2 vCPU, 4 GiB RAM).",[],{"_key":994,"_type":8,"children":995,"level":154,"listItem":155,"markDefs":1008,"style":16},"10c644450969",[996,1000,1004],{"_key":997,"_type":12,"marks":998,"text":999},"dda8a26323f6",[],"İşletim sistemi olarak ",{"_key":1001,"_type":12,"marks":1002,"text":1003},"be8e6e2e7df7",[35],"Amazon Linux 2 AMI (HVM), SSD Volume Type",{"_key":1005,"_type":12,"marks":1006,"text":1007},"15655af52f1b",[]," kullandım. Bu, SSD diskli ve yüksek performanslı sanallaştırma destekli bir Linux dağıtımıdır. Siz ihtiyacınıza göre farklı bir işletim sistemi tercih edebilirsiniz.",[],{"_key":1010,"_type":8,"children":1011,"level":154,"listItem":155,"markDefs":1024,"style":16},"e10d40d694ac",[1012,1016,1020],{"_key":1013,"_type":12,"marks":1014,"text":1015},"43724e76667b",[],"Depolama (Storage) ayarlarında varsayılan olarak sunulan ",{"_key":1017,"_type":12,"marks":1018,"text":1019},"ea89fc02b053",[35],"8 GiB EBS SSD",{"_key":1021,"_type":12,"marks":1022,"text":1023},"60633beed995",[],"’yi kullandım. Daha fazla alana ihtiyacınız varsa 16 GiB veya üzerine çıkarabilirsiniz.",[],{"_key":1026,"_type":8,"children":1027,"level":154,"listItem":155,"markDefs":1047,"style":16},"38a24985c01b",[1028,1032,1036,1039,1043],{"_key":1029,"_type":12,"marks":1030,"text":1031},"67348d468d70",[],"SSH erişimi için, ",{"_key":1033,"_type":12,"marks":1034,"text":1035},"c6f4f5d04e75",[35],"TCP 22. portu sadece kendi IP adresimle (örneğin",{"_key":1037,"_type":12,"marks":1038,"text":476},"637c847edd52",[],{"_key":1040,"_type":12,"marks":1041,"text":1042},"9c9fba830675",[35],"203.0.113.5/32) sınırladım.",{"_key":1044,"_type":12,"marks":1045,"text":1046},"07855001c698",[]," Böylece sunucuya yalnızca kendi cihazımdan bağlanabiliyorum. Bu sayede başkalarının sunucuma bağlanmasını engellemiş oldum.",[],{"_key":1049,"_type":8,"children":1050,"level":154,"listItem":155,"markDefs":1067,"style":16},"95dd3f493d9e",[1051,1055,1059,1063],{"_key":1052,"_type":12,"marks":1053,"text":1054},"125cc3a51e99",[35],"Güvenlik grubu (Security Group)",{"_key":1056,"_type":12,"marks":1057,"text":1058},"392218401bbb",[]," ayarlarında yalnızca ",{"_key":1060,"_type":12,"marks":1061,"text":1062},"f0dff1f716ae",[35],"SSH (port 22)",{"_key":1064,"_type":12,"marks":1065,"text":1066},"2cdd17b7c67b",[]," erişimine izin verdim.",[],{"_key":1069,"_type":8,"children":1070,"level":154,"listItem":155,"markDefs":1079,"style":16},"9fb00ff27f37",[1071,1075],{"_key":1072,"_type":12,"marks":1073,"text":1074},"bef2f2c0db7b",[35],"Outbound kurallarını",{"_key":1076,"_type":12,"marks":1077,"text":1078},"b472d3f73281",[]," AWS’nin varsayılan haliyle bıraktım (her yere açık). Böylece hem npm paketlerini çekebiliyor hem de Worldometer gibi sitelere veri kazımı için istek atabiliyorum.",[],{"_key":1081,"_type":8,"children":1082,"markDefs":1087,"style":134},"6334e266edae",[1083],{"_key":1084,"_type":12,"marks":1085,"text":1086},"bf582326b8e9",[],"2. Sunucuya Giriş ve Temel Kurulum",[],{"_key":1089,"_type":8,"children":1090,"markDefs":1095,"style":16},"a600fd2c273a",[1091],{"_key":1092,"_type":12,"marks":1093,"text":1094},"449b956847db",[35],"SSH ile Bağlanma",[],{"_key":1097,"_type":389,"code":1098,"language":1099},"213152aa6758","ssh -i “~/my-key.pem” ec2-user@EC2_PUBLIC_IP","markdown",{"_key":1101,"_type":8,"children":1102,"markDefs":1107,"style":16},"9192d95f7f23",[1103],{"_key":1104,"_type":12,"marks":1105,"text":1106},"915377289f44",[35],"Sistem Güncellemesi",[],{"_key":1109,"_type":389,"code":1110,"language":1099},"c02dde6d9aed","sudo yum update -y",{"_key":1112,"_type":8,"children":1113,"markDefs":1118,"style":16},"75b9abe1da34",[1114],{"_key":1115,"_type":12,"marks":1116,"text":1117},"6bb1eabe02e5",[35],"Chrome/Chromium Bağımlılıkları",[],{"_key":1120,"_type":8,"children":1121,"markDefs":1126,"style":16},"760354e1984c",[1122],{"_key":1123,"_type":12,"marks":1124,"text":1125},"ae1827b4fa27",[],"Puppeteer kullandığımız için gerekli kütüphaneleri ekledim:",[],{"_key":1128,"_type":389,"code":1129,"language":1099},"794d800c41b4","sudo amazon-linux-extras install epel -ysudo yum install -y \\ wget \\ bind-utils \\ libX11 \\ alsa-lib \\ gtk3 \\ ipa-gothic-fonts",{"_key":1131,"_type":8,"children":1132,"markDefs":1137,"style":16},"6a7fa0eca49d",[1133],{"_key":1134,"_type":12,"marks":1135,"text":1136},"264b20b6390f",[35],"Node.js ve Git Kurulumu",[],{"_key":1139,"_type":389,"code":1140,"language":1099},"f6ba9edf0fe6","curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bashsource ~/.bashrcnvm install --ltsgit --version || sudo yum install git -y",{"_key":1142,"_type":8,"children":1143,"markDefs":1148,"style":125},"9509016e3a9f",[1144],{"_key":1145,"_type":12,"marks":1146,"text":1147},"f818a3ba49ec",[],"3. Proje Kurulumu",[],{"_key":1150,"_type":8,"children":1151,"markDefs":1156,"style":16},"0ab2cb9c9035",[1152],{"_key":1153,"_type":12,"marks":1154,"text":1155},"7249ee7fb236",[35],"Repo Klonlama",[],{"_key":1158,"_type":389,"code":1159,"language":1099},"7cd5100fbd8f","git clone https://github.com/kullanici-adiniz/proje-ismi.gitcd proje-ismi",{"_key":1161,"_type":8,"children":1162,"markDefs":1167,"style":16},"96497c1d0b29",[1163],{"_key":1164,"_type":12,"marks":1165,"text":1166},"691eb9cf4c77",[35],"Çevresel Değişkenler",[],{"_key":1169,"_type":8,"children":1170,"markDefs":1175,"style":16},"3d8ed05e2ab7",[1171],{"_key":1172,"_type":12,"marks":1173,"text":1174},"d9131262a127",[],"Kök dizinde bir .env dosyası oluşturun:\nKök dizindeki .env → Geliştirme ortamı için",[],{"_key":1177,"_type":8,"children":1178,"level":154,"listItem":155,"markDefs":1183,"style":16},"b9828ef12d1d",[1179],{"_key":1180,"_type":12,"marks":1181,"text":1182},"ee960419789f",[],"Docker container’larının birbiriyle iletişim kurmasını sağlar",[],{"_key":1185,"_type":8,"children":1186,"level":154,"listItem":155,"markDefs":1191,"style":16},"5afcfd104cf6",[1187],{"_key":1188,"_type":12,"marks":1189,"text":1190},"f86c188b3ba4",[],"Local Elasticsearch bağlantı bilgilerini tutar (http://localhost:9200)",[],{"_key":1193,"_type":8,"children":1194,"level":154,"listItem":155,"markDefs":1199,"style":16},"0a023208db9d",[1195],{"_key":1196,"_type":12,"marks":1197,"text":1198},"ebb5e736864a",[],"Geliştirme sırasında hızlı test yapmamızı sağlar",[],{"_key":1201,"_type":8,"children":1202,"markDefs":1221,"style":16},"a446a6103517",[1203,1207,1210,1214,1217],{"_key":1204,"_type":12,"marks":1205,"text":1206},"d1da2e44e3cf",[35],"Ayrıca bir",{"_key":1208,"_type":12,"marks":1209,"text":476},"697cbf06b0b4",[],{"_key":1211,"_type":12,"marks":1212,"text":1213},"878b7fd57660",[35],".env",{"_key":1215,"_type":12,"marks":1216,"text":476},"ca351d83230b",[],{"_key":1218,"_type":12,"marks":1219,"text":1220},"885a5687685f",[35],"daha → Production ortamı için",[],{"_key":1223,"_type":8,"children":1224,"level":154,"listItem":155,"markDefs":1229,"style":16},"b675b4bca5d8",[1225],{"_key":1226,"_type":12,"marks":1227,"text":1228},"e32d6148fcca",[],"Gerçek Elasticsearch cloud bağlantı bilgileri",[],{"_key":1231,"_type":8,"children":1232,"level":154,"listItem":155,"markDefs":1237,"style":16},"62a80c46cf3f",[1233],{"_key":1234,"_type":12,"marks":1235,"text":1236},"da0557e7ceba",[],"API key’ler ve güvenlik bilgileri",[],{"_key":1239,"_type":8,"children":1240,"level":154,"listItem":155,"markDefs":1245,"style":16},"9b6e16b90c1e",[1241],{"_key":1242,"_type":12,"marks":1243,"text":1244},"9fe93d174f45",[],"Asla GitHub’a yüklenmemesi gereken hassas veriler",[],{"_key":1247,"_type":389,"code":1248},"242ea8bf567c","ELASTICSEARCH_HOST=https://your-es-endpoint:9200INDEX_NAME=world_population_dataELASTIC_USERNAME=elasticELASTIC_PASSWORD=changemeUSER_AGENT=Mozilla/...",{"_key":1250,"_type":8,"children":1251,"markDefs":1256,"style":16},"eabe548a7478",[1252],{"_key":1253,"_type":12,"marks":1254,"text":1255},"dc37d0efa2bc",[35],"Bağımlılıkları Yükleme",[],{"_key":1258,"_type":389,"code":1259,"language":1099},"79d7b86bd3f0","npm install",{"_key":1261,"_type":8,"children":1262,"markDefs":1267,"style":125},"2189bb4d025b",[1263],{"_key":1264,"_type":12,"marks":1265,"text":1266},"f8e82a06edf1",[],"4. Kesintisiz Çalıştırma ve İzleme",[],{"_key":1269,"_type":8,"children":1270,"markDefs":1275,"style":16},"a94aab86bb67",[1271],{"_key":1272,"_type":12,"marks":1273,"text":1274},"d774fcf18548",[35],"PM2 ile Süreklilik",[],{"_key":1277,"_type":389,"code":1278,"language":1099},"aefa8e10a645","npm install -g pm2pm2 start main.js --name population-scraperpm2 savepm2 startup",{"_key":1280,"_type":8,"children":1281,"level":154,"listItem":155,"markDefs":1286,"style":16},"a46042be93b2",[1282],{"_key":1283,"_type":12,"marks":1284,"text":1285},"0ee2a3203b20",[],"pm2 startup komutu, EC2 yeniden başlasa bile PM2 servisinin otomatik ayağa kalkmasını sağlar. Bu sayede bu işlemi her seferinde elle başlatmamıza gerek kalmaz",[],{"_key":1288,"_type":8,"children":1289,"level":154,"listItem":155,"markDefs":1294,"style":16},"4a4b761d3c04",[1290],{"_key":1291,"_type":12,"marks":1292,"text":1293},"ee4073746e2f",[],"pm2 save ise mevcut process listesini kaydeder.",[],{"_key":1296,"_type":8,"children":1297,"markDefs":1302,"style":16},"a997edc08598",[1298],{"_key":1299,"_type":12,"marks":1300,"text":1301},"a23faba69b36",[35],"Log Takibi",[],{"_key":1304,"_type":8,"children":1305,"level":154,"listItem":155,"markDefs":1310,"style":16},"0fb8c70fb2e9",[1306],{"_key":1307,"_type":12,"marks":1308,"text":1309},"03c389166393",[],"Gerçek zamanlı log izlemek için:",[],{"_key":1312,"_type":389,"code":1313,"language":1099},"3827eab15703","pm2 logs population-scraper",{"_key":1315,"_type":8,"children":1316,"level":154,"listItem":155,"markDefs":1321,"style":16},"1a8de57e816d",[1317],{"_key":1318,"_type":12,"marks":1319,"text":1320},"6bcb0f2ccced",[],"Hatayı veya ilerlemeyi konuya göre renkli ve zaman damgalı görebiliyorsunuz.",[],{"_key":1323,"_type":8,"children":1324,"markDefs":1329,"style":125},"786c91394d17",[1325],{"_key":1326,"_type":12,"marks":1327,"text":1328},"35dfdf94d0d5",[],"5. Canlı Kazıma Döngüsü",[],{"_key":1331,"_type":8,"children":1332,"level":154,"listItem":155,"markDefs":1337,"style":16},"c05fb74b7631",[1333],{"_key":1334,"_type":12,"marks":1335,"text":1336},"c6d352f1bfb5",[],"main.js içinde yazdığımprocessDataWithEnergy() fonksiyonunu inceleyelim:",[],{"_key":1339,"_type":8,"children":1340,"level":154,"listItem":155,"markDefs":1349,"style":16},"19261fbdb65f",[1341,1345],{"_key":1342,"_type":12,"marks":1343,"text":1344},"b8f98fa7e144",[35],"30 dakikada bir",{"_key":1346,"_type":12,"marks":1347,"text":1348},"ac10b9853a22",[]," tekrar eden bir zamanlayıcıyla (setInterval) tetikleniyor.",[],{"_key":1351,"_type":8,"children":1352,"level":154,"listItem":155,"markDefs":1357,"style":16},"7c6a5986dc45",[1353],{"_key":1354,"_type":12,"marks":1355,"text":1356},"7dd7f28b862a",[],"Önce dinamik kazıma (fetchWorldDataDynamic, fetchCountryDataDynamic) deneniyor.",[],{"_key":1359,"_type":8,"children":1360,"level":154,"listItem":155,"markDefs":1365,"style":16},"68d5ffee44b5",[1361],{"_key":1362,"_type":12,"marks":1363,"text":1364},"d4d60652a9d6",[],"Eğer dinamik kazımada bir hata oluşursa, kod bloğunda yakalanıp null dönüyor; bu durumda statik kazıma ya da hata yönetimi devreye giriyor.",[],{"_key":1367,"_type":8,"children":1368,"level":154,"listItem":155,"markDefs":1373,"style":16},"8f365de8552e",[1369],{"_key":1370,"_type":12,"marks":1371,"text":1372},"613d18902bf1",[],"Böylece EC2 üzerinde 7/24 veri akışı kesintisiz olarak devam ediyor.",[],{"_key":1375,"_type":8,"children":1376,"markDefs":1381,"style":125},"7bcd2cf25034",[1377],{"_key":1378,"_type":12,"marks":1379,"text":1380},"0182fb3fc070",[],"6. GitHub Pipeline ve Cloudflare Entegrasyonu",[],{"_key":1383,"_type":8,"children":1384,"markDefs":1389,"style":16},"018abf24fdf3",[1385],{"_key":1386,"_type":12,"marks":1387,"text":1388},"6127bf9e7266",[],"Projede domain, pipeline ve otomatik deployment tarafını tamamen otomatize ettim.",[],{"_key":1391,"_type":8,"children":1392,"markDefs":1397,"style":125},"fbcb8f9f07de",[1393],{"_key":1394,"_type":12,"marks":1395,"text":1396},"3d4c00338b68",[],"Domain & Cloudflare",[],{"_key":1399,"_type":8,"children":1400,"markDefs":1413,"style":16},"b269b4959b92",[1401,1405,1409],{"_key":1402,"_type":12,"marks":1403,"text":1404},"305842ce1a92",[],"Domain’i Namecheap’ten(Natro) aldıktan sonra Cloudflare’e taşıdık.\nBu sayede ",{"_key":1406,"_type":12,"marks":1407,"text":1408},"7080740d860d",[35],"GitHub — Sunucu — Domain",{"_key":1410,"_type":12,"marks":1411,"text":1412},"36e11bac9597",[]," arasında bir köprü kuruldu.\nArtık main branch’e push attığım anda güncelleme otomatik olarak canlıya geçiyor.",[],{"_key":1415,"_type":8,"children":1416,"markDefs":1421,"style":16},"6d82a24ec871",[1417],{"_key":1418,"_type":12,"marks":1419,"text":1420},"32c4b0a34bc4",[],"Cloudflare tarafında:",[],{"_key":1423,"_type":8,"children":1424,"level":154,"listItem":155,"markDefs":1429,"style":16},"211a81bbbdea",[1425],{"_key":1426,"_type":12,"marks":1427,"text":1428},"68db29660686",[],"CDN sayesinde siteye dünyanın her yerinden hızlı erişim sağlanıyor",[],{"_key":1431,"_type":8,"children":1432,"level":154,"listItem":155,"markDefs":1437,"style":16},"edfcbb43010a",[1433],{"_key":1434,"_type":12,"marks":1435,"text":1436},"67f7c982e0b8",[],"DDoS koruması ve cache optimizasyonu da bonus oldu",[],{"_key":1439,"_type":8,"children":1440,"markDefs":1445,"style":1446},"877791d140af",[1441],{"_key":1442,"_type":12,"marks":1443,"text":1444},"4c30ebec929b",[],"Son Olarak Yaptığımız İşlemleri Kısaca Özetleyelim",[],"h5",{"_key":1448,"_type":8,"children":1449,"markDefs":1458,"style":16},"82a22868ac0d",[1450,1454],{"_key":1451,"_type":12,"marks":1452,"text":1453},"a5104e91e7b5",[35],"Güvenlik grubu",{"_key":1455,"_type":12,"marks":1456,"text":1457},"c4ba6fdaf1d0",[]," sadece gerekli portlara izin veriyor (SSH, Elasticsearch).",[],{"_key":1460,"_type":8,"children":1461,"markDefs":1470,"style":16},"bf2baa965703",[1462,1466],{"_key":1463,"_type":12,"marks":1464,"text":1465},"d0af9281d0ff",[35],"Amazon Linux 2",{"_key":1467,"_type":12,"marks":1468,"text":1469},"c19741f40a26",[]," üzerinde Node.js, Chrome bağımlılıkları ve Git kurulumu tamamlandı.",[],{"_key":1472,"_type":8,"children":1473,"markDefs":1482,"style":16},"5093c79ad7b9",[1474,1478],{"_key":1475,"_type":12,"marks":1476,"text":1477},"d85a29d17784",[35],"Proje",{"_key":1479,"_type":12,"marks":1480,"text":1481},"ebf67dde1b0e",[]," klonlanıp, .env ile konfigürasyonu yapıldı.",[],{"_key":1484,"_type":8,"children":1485,"markDefs":1494,"style":16},"a45b093814e7",[1486,1490],{"_key":1487,"_type":12,"marks":1488,"text":1489},"c2cca5486ad0",[35],"PM2",{"_key":1491,"_type":12,"marks":1492,"text":1493},"481f6c5f60e6",[]," ile kesintisiz, otomatik başlatılan bir servis olarak uygulama ayağa kaldırıldı.",[],{"_key":1496,"_type":8,"children":1497,"markDefs":1506,"style":16},"5528736ca4c2",[1498,1502],{"_key":1499,"_type":12,"marks":1500,"text":1501},"e5974299fac5",[35],"Zamanlayıcı",{"_key":1503,"_type":12,"marks":1504,"text":1505},"7507ca480385",[]," sayesinde 30 dakikada bir, dinamik (ve gerekirse fallback statik) kazıma işlemi çalıştırılıyor.",[],{"_key":1508,"_type":8,"children":1509,"markDefs":1518,"style":16},"6ccce4dff471",[1510,1514],{"_key":1511,"_type":12,"marks":1512,"text":1513},"ff9f89e43968",[35],"Cloudflare entegrasyonu",{"_key":1515,"_type":12,"marks":1516,"text":1517},"298a50d75bff",[]," ile domain yönetimi, SSL ve CDN yapılandırması tamamlandı. Artık siteye dünyanın her yerinden hızlı ve güvenli şekilde erişilebiliyor.",[],{"_key":1520,"_type":8,"children":1521,"markDefs":1537,"style":16},"880f838fe557",[1522,1526,1530,1533],{"_key":1523,"_type":12,"marks":1524,"text":1525},"76b93eb6817f",[35],"GitHub Actions pipeline",{"_key":1527,"_type":12,"marks":1528,"text":1529},"f47abaf16518",[]," devreye alındı: her push sonrası testler çalışıyor, son sürüm otomatik olarak EC2’ye deploy ediliyor ve ",{"_key":1531,"_type":12,"marks":1532,"text":1489},"e6ed2b7b492f",[35],{"_key":1534,"_type":12,"marks":1535,"text":1536},"397ea8b9b930",[]," aracılığıyla sıfır kesintiyle güncelleniyor.",[],{"_key":1539,"_type":8,"children":1540,"markDefs":1545,"style":16},"b1e57a2212f7",[1541],{"_key":1542,"_type":12,"marks":1543,"text":1544},"8d105a385454",[],"Bu adımlarla, t3.medium Free Tier EC2 örneğinde sorunsuz şekilde canlı kazıma ortamı kurmuş oldum. Aynı adımları inceleyerek sizler de rahatlıkla yapabilirsiniz. İhtiyaç duymanız halinde anlık hata bildirimleri alabileceğiniz ayarları da yapabilirsiniz",[],{"_key":1547,"_type":8,"children":1548,"markDefs":1553,"style":125},"d2dd4293891a",[1549],{"_key":1550,"_type":12,"marks":1551,"text":1552},"d5283374f451",[],"NOT !!! LÜTFEN AWS FATURALANDIRMA ALARMLARINI ETKİNLEŞTİRMEYİ UNUTMAYIN :)",[],{"_key":1555,"_type":8,"children":1556,"markDefs":1561,"style":125},"5fd6aab008be",[1557],{"_key":1558,"_type":12,"marks":1559,"text":1560},"b590bdd368fc",[],"Sonuç ve Değerlendirme",[],{"_key":1563,"_type":8,"children":1564,"markDefs":1569,"style":16},"0f4c8a421db7",[1565],{"_key":1566,"_type":12,"marks":1567,"text":1568},"8373880adc30",[],"Bu projede asıl hedefim; kazıdığım nüfus verilerini tek bir Elasticsearch indeksinde toplayarak hem ülke hem de dünya verilerini birlikte yönetilebilir hale getirmekti.",[],{"_key":1571,"_type":8,"children":1572,"markDefs":1577,"style":16},"6c0b761ec35b",[1573],{"_key":1574,"_type":12,"marks":1575,"text":1576},"eb4bb2439e49",[],"Bu süreçte Elasticsearch’ün ölçeklenebilirliği, güçlü sorgu yapısı ve Kibana ile olan uyumu gerçekten fark yarattı. Proje boyunca elimdeki veriyi sadece saklamadım; anlamlı hale getirip görselleştirerek canlı bir sisteme dönüştürdüm. Umarım projemi ve projemi anlattığım bu yazıyı sevmişsinizdir :)",[],{"_key":1579,"_type":8,"children":1580,"markDefs":1585,"style":1587},"df7d18d511a0",[1581],{"_key":1582,"_type":12,"marks":1583,"text":109},"4edfab0d231e",[1584,35],"36742b64f0ab",[1586],{"_key":1584,"_type":79,"href":116},"h6",[231],{"_type":18,"asset":1590},{"_ref":1591,"_type":21},"image-c986841bcc15518c76c7d577411577bc147a99ab-2400x1260-webp","2025-12-26T17:55:32.162Z",[],"Elasticsearch İle Gerçek Zamanlı Nüfus Verisi Toplama ve Görselleştirme",1776095187714]