[{"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:\u002F\u002Fwww.worldometers.info\u002F",{"_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:\u002F\u002Ffehu-zone\u002Fpopulation-scraper-node",{"_key":100,"_type":79,"href":114},"https:\u002F\u002Fgithub.com\u002Ffehu-zone\u002Fpopulation-data-app",{"_key":108,"_type":79,"href":116},"https:\u002F\u002Fahmetkaradas.site\u002Fcontact",{"_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\u002FHTTP 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\u002F5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\u002F537.36 (KHTML, like Gecko) Chrome\u002F91.0.4472.124 Safari\u002F537.36\",\n  },\n  COUNTRIES_URL:\n    process.env.COUNTRIES_URL ||\n    \"https:\u002F\u002Fwww.worldometers.info\u002Fworld-population\u002Fpopulation-by-country\u002F?t=\" +\n      Date.now(),\n  WORLD_URL:\n    process.env.WORLD_URL || \"https:\u002F\u002Fwww.worldometers.info\u002Fworld-population\u002F\",\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\u002Felasticsearch\";\nimport config from \"..\u002Fconfig\u002Findex.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    \u002F\u002F 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    \u002F\u002F 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 \".\u002Futils.js\";\nimport config from \"..\u002Fconfig\u002Findex.js\";\n\npuppeteer.use(StealthPlugin());\n\nexport const fetchCountryDataDynamic = async () => {\n  let browser;\n  let page;\n\n  try {\n    \u002F\u002F 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    \u002F\u002F 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    \u002F\u002F 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    \u002F\u002F 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    \u002F\u002F 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(\u002F\\u00a0\u002Fg, \" \") \u002F\u002F Özel boşluk karakterlerini temizle\n            .trim()\n        );\n      });\n    });\n\n    \u002F\u002F 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 \"..\u002Fconfig\u002Findex.js\";\nimport { parseNumber } from \".\u002Futils.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      \u002F\u002F 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 \"..\u002Fconfig\u002Findex.js\";\nimport { parseNumber } from \".\u002Futils.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        \u002F\u002F 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 \"..\u002Felastic\u002Fclient.js\";\nimport config from \"..\u002Fconfig\u002Findex.js\";\n\nexport const parseNumber = (str, isPercentage = false) => {\n  if ([null, undefined, \"\"].includes(str)) return 0; \u002F\u002F Null değerler için 0\n\n  const cleaned = String(str)\n    .replace(\u002F[^\\d.-]\u002Fg, \"\")\n    .replace(\u002F^\\-\u002Fg, \"-\");\n\n  const number = parseFloat(cleaned);\n  return Number.isNaN(number) ? 0 : number; \u002F\u002F NaN durumunda 0\n};\n\nexport const cleanCountryName = (name) => {\n  const COUNTRY_NAME_MAPPING = {\n    \u002F* ... *\u002F\n  };\n\n  return (\n    COUNTRY_NAME_MAPPING[name] ||\n    name\n      .replace(\u002F\\[.*?\\]\u002Fg, \"\")\n      .replace(\u002F\\(.*?\\)\u002Fg, \"\")\n      .trim()\n  );\n};\n\nexport const bulkIndexCountries = async (countries) => {\n  try {\n    \u002F\u002F 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      \u002F* ... *\u002F\n    });\n\n    const body = countries.flatMap((country) => [\n      \u002F* ... *\u002F\n    ]);\n\n    const { body: bulkResponse } = await client.bulk({\n      \u002F* ... *\u002F\n    });\n\n    return {\n      \u002F* ... *\u002F\n    };\n  } catch (error) {\n    return {\n      \u002F* ... *\u002F\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\u002F\u002F .env dosyasından bilgileri alıyoruz.\n\u002F\u002F Eğer ELASTICSEARCH_HOST tanımlı değilse, varsayılan olarak yerel sunucuyu kullan.\nconst ELASTICSEARCH_HOST =\n  process.env.ELASTICSEARCH_HOST || \"http:\u002F\u002Flocalhost:9200\u002F\";\n\nimport { fetchCountryDataDynamic } from \".\u002Fscraper\u002FcountryDataDynamic.js\";\nimport { fetchWorldDataDynamic } from \".\u002Fscraper\u002FworldDataDynamic.js\";\nimport { initIndex, client } from \".\u002Felastic\u002Fclient.js\";\nimport ProgressBar from \"progress\";\nimport { updateCurrentSnapshot } from \".\u002Felastic\u002Fclient.js\";\n\n\u002F\u002F 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\u002F\u002F Geliştirilmiş Veri Doğrulama\nconst validateData = (worldData, countryData) => {\n  const warnings = [];\n  const errors = [];\n  const EXPECTED_COUNTRIES = 235;\n\n  \u002F\u002F Dünya verisi kontrolleri\n  if (!worldData?.current_population) {\n    errors.push(\"Dünya nüfus verisi eksik\");\n  }\n\n  \u002F\u002F Ü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  \u002F\u002F 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  \u002F\u002F 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\u002F\u002F 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) \u002F 1e6;\n    const wallSeconds = elapsedTime[0] + elapsedTime[1] \u002F 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\u002F\u002F Ana İşlem Akışı\nconst processData = async () => {\n  try {\n    logger.info(\"Scraping süreci başlatılıyor...\");\n\n    \u002F\u002F Elasticsearch hazırlığı\n    await initIndex();\n\n    \u002F\u002F 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    \u002F\u002F 2. Bekleme süresi\n    logger.info(\"Dünya verisi alındıktan sonra 20 saniye bekleniyor...\");\n    await delay(20000);\n\n    \u002F\u002F 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    \u002F\u002F Sonuçları işle\n    const results = { world: worldData, country: countryData };\n    logResults(results);\n\n    \u002F\u002F Validasyon\n    const validation = validateData(results.world, results.country);\n    handleValidation(validation);\n\n    \u002F\u002F 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    \u002F\u002F 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\u002F\u002F Enerji ölçümü dahil ana işlem çağrısı\nconst processDataWithEnergy = async () => {\n  await measureEnergyConsumption(processData, \"processData\");\n};\n\n\u002F\u002F 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    \u002F\u002F 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    \u002F\u002F Ü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) \u002F validAges.length).toFixed(\n        1\n      )\n    : \"N\u002FA\";\n};\n\nconst delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));\n\nconsole.clear();\nprocessDataWithEnergy();\nsetInterval(processDataWithEnergy, 1800000); \u002F\u002F 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\u002Fdesktop 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:\u002F\u002Fmy-deployment:9243\u002Fapp\u002Fdashboards#\u002Fview\u002F7adfa750-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\u002Fiframe>","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:\u002F\u002Fwww.elastic.co\u002Fblog\u002Fhow-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\u002F32) 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 “~\u002Fmy-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\u002FChromium 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:\u002F\u002Fraw.githubusercontent.com\u002Fnvm-sh\u002Fnvm\u002Fv0.39.3\u002Finstall.sh | bashsource ~\u002F.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:\u002F\u002Fgithub.com\u002Fkullanici-adiniz\u002Fproje-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:\u002F\u002Flocalhost: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:\u002F\u002Fyour-es-endpoint:9200INDEX_NAME=world_population_dataELASTIC_USERNAME=elasticELASTIC_PASSWORD=changemeUSER_AGENT=Mozilla\u002F...",{"_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\u002F24 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",1789035750438]