Python Wikidata SPARQL sorgularında 504 / 403 hatası

Katılım
8 Eylül 2018
Mesajlar
9.664
Makaleler
8
Çözümler
226
Yer
İstanbul
Arkadaşlar merhaba. Python ile Wikidata üzerinden turistik noktaların listesini çekmek için bir script yazdım. Script, SPARQL sorgusu kullanıyor ve her seferinde 50 kayıt çekmeye çalışıyor. Fakat sorguları çalıştırırken sürekli şu hatalarla karşılaşıyorum:

504 Gateway Timeout > Sunucu yanıt vermiyor.
403 Forbidden > Erişim engellendi.

SPARQL query failed: 504 Server Error: Gateway Timeout for url: https://query.wikidata.org/sparql?query=%0A++++++++++++SELECT+DISTINCT+%3Fitem+%3FitemLabel+%3Fdesc+%3Fcoord+%3FinstanceLabel+%3FadminLabel+%3FcountryLabel+WHERE+%7B%0A++++++++++++++%3Fitem+wdt%3AP31%2Fwdt%3AP279%2A+wd%3AQ570116+.%0A++++++++++++++%3Fitem+wdt%3AP625+%3Fcoord+.%0A++++++++++++++OPTIONAL+%7B+%3Fitem+schema%3Adescription+%3Fdesc+FILTER%28LANG%28%3Fdesc%29%3D%22en%22%29+%7D%0A++++++++++++++OPTIONAL+%7B+%3Fitem+wdt%3AP131+%3Fadmin+.+%3Fadmin+rdfs%3Alabel+%3FadminLabel+FILTER%28LANG%28%3FadminLabel%29%3D%22en%22%29+%7D%0A++++++++++++++OPTIONAL+%7B+%3Fitem+wdt%3AP17+%3Fcountry+.+%3Fcountry+rdfs%3Alabel+%3FcountryLabel+FILTER%28LANG%28%3FcountryLabel%29%3D%22en%22%29+%7D%0A++++++++++++++SERVICE+wikibase%3Alabel+%7B+bd%3AserviceParam+wikibase%3Alanguage+%22en%22.+%7D%0A++++++++++++%7D%0A++++++++++++LIMIT+50+OFFSET+0%0A++++++++++++. Waiting 10s before retry...


Kodumda zaten, Retry mekanizması Exponential backoff (bekleme süresini artırma) User-Agent tanımlaması gibi önlemler var. Buna rağmen bazı sınıflarda veri çekemiyorum.

Sormak istediğim ise, SPARQL sorgularını daha güvenli ve stabil çalıştırmanın yolları nelerdir? Bu hataları önlemek için en iyi uygulama stratejileri nelerdir?

Veri kazımadan biraz uzak kaldığım için webdriver eksikliği falan mı var diye düşündüm ama ben Selenium veya tarayıcı otomasyonu kullanmıyorum. Tamamen Python requests ile SPARQL API'sine HTTP istekleri atıyorum.

Yapay zekaya da bayağı danıştım, videolara falan da baktım ama son çare buraya sormak oldu. Gözden kaçırdığım bir şey olabilir mi? Koda Türkçe yorum satırları da ekledim, siz de daha rahat incelersiniz.

Python:
# -*- coding: utf-8 -*-
"""
Created on Thu Oct 2 2025.

@author: Emre.
"""

import requests.
import json.
import time.
from geopy.geocoders import Nominatim.
from haversine import haversine, Unit.
from tqdm import tqdm.

# ---------- Ayarlar ----------
WIKIDATA_SPARQL_ENDPOINT = "https://query.wikidata.org/sparql"
OUTPUT_FILE = "spots.json"
MIN_RECORDS = 1001.
Dedupe_THRESHOLD_M = 20.
GEOCODER_USER_AGENT = "intelliverse_spots_exporter_emre_v3"
SPARQL_LIMIT = 50 # Daha düşük limit.
CHUNK_DELAY = 10 # Daha uzun bekleme.
# ------------------------------------------------

# ### Sınıf Sıralaması Güncellendi ###
# En büyük ve en yavaş sınıf (Q570116) listenin sonuna alındı.
CLASSES = [
 "Q33506", # museum (müze - daha spesifik)
 "Q22698", # scenic viewpoint (manzara noktası)
 "Q7889", # beach (plaj)
 "Q40257", # monument (anıt)
 "Q23442", # park.
 "Q811430", # park alternate.
 "Q16917", # artwork (sanat eseri)
 "Q4989906", # mural (duvar resmi - en spesifiklerden biri)
 "Q570116", # tourist attraction (turistik cazibe - EN GENEL VE YAVAŞ)
]

# run_sparql ve diğer fonksiyonlar aynı kalır.
# (Timeout 120, retries 7 ve basitleştirilmiş SPARQL sorgusu önceki yanıttan alınmıştır.)

def run_sparql(query, retries=7, wait=10):
 headers = {"Accept": "application/sparql-results+json"}
 for attempt in range(retries):
 try:
 r = requests.get(WIKIDATA_SPARQL_ENDPOINT, params={"query": query}, headers=headers, timeout=120)
 if r.status_code == 429:
 print(f"Too Many Requests (429). Waiting {wait}s before retry...")
 time.sleep(wait)
 wait *= 2
 continue.
 r.raise_for_status()
 return r.json()
 except requests.exceptions.RequestException as e:
 print(f"SPARQL query failed: {e}. Waiting {wait}s before retry...")
 time.sleep(wait)
 wait *= 2
 print("SPARQL query failed after retries.")
 return None.

def parse_coord(coord_literal):
 if not coord_literal:
 return None.
 if coord_literal.startswith("Point(") and coord_literal.endswith(")"):
 inner = coord_literal[len("Point("):-1]
 parts = inner.split()
 if len(parts) >= 2:
 lon = float(parts[0])
 lat = float(parts[1])
 return lat, lon.
 nums = [float(x) for x in coord_literal.replace('(', ' ').replace(')', ' ').replace(',', ' ').split() if any(ch.isdigit() for ch in x)]
 if len(nums) >= 2:
 return nums[-1], nums[-2]
 return None.

def normalize_type(instance_label):
 if not instance_label:
 return "Attraction"
 lab = instance_label.lower()
 if "beach" in lab: return "Beach"
 if "museum" in lab: return "Museum"
 if "park" in lab: return "Park"
 if "view" in lab or "lookout" in lab or "scenic" in lab: return "Scenic viewpoint"
 if "monument" in lab or "memorial" in lab: return "Monument"
 if "art" in lab or "mural" in lab: return "Mural"
 if "landmark" in lab or "tourist" in lab: return "Landmark"
 return instance_label.

def reverse_geocode_city(geolocator, lat, lon):
 try:
 location = geolocator.reverse((lat, lon), exactly_one=True, language="en", timeout=10)
 time.sleep(1.1)
 if not location:
 return None.
 adr = location.raw.get('address', {})
 for key in ("city", "town", "village", "municipality", "hamlet", "county"):
 if adr.get(key):
 return adr.get(key)
 return adr.get('state') or adr.get('country')
 except Exception:
 time.sleep(1.1)
 return None.

def dedupe_places(places):
 kept = []
 for p in places:
 lat = p["coordinates"]["lat"]
 lon = p["coordinates"]["lon"]
 pair = (lat, lon)
 too_close = False.
 for k in kept:
 kpair = (k["coordinates"]["lat"], k["coordinates"]["lon"])
 dist_m = haversine(pair, kpair, unit=Unit.METERS)
 if dist_m <= Dedupe_THRESHOLD_M:
 if not k.get("description") and p.get("description"):
 k["description"] = p["description"]
 if not k.get("city") and p.get("city"):
 k["city"] = p["city"]
 too_close = True.
 break.
 if not too_close:
 kept.append(p)
 return kept.

def main():
 geolocator = Nominatim(user_agent=GEOCODER_USER_AGENT)
 spots = []

 for cls in CLASSES:
 print(f"Sınıf işleniyor: {cls}")
 offset = 0
 while True:
 # Basitleştirilmiş SPARQL sorgusu:
 query = f"""
 SELECT DISTINCT ?item ?itemLabel ?desc ?coord ?instanceLabel ?adminLabel ?countryLabel WHERE {{
 {{ ?item wdt:P31 wd:{cls} . }}
 UNION.
 {{ ?item wdt:P31/wdt:P279 wd:{cls} . }}

 ?item wdt:P625 ?coord .
 OPTIONAL {{ ?item schema:description ?desc FILTER(LANG(?desc)="en") }}
 OPTIONAL {{ ?item wdt:P131 ?admin . ?admin rdfs:label ?adminLabel FILTER(LANG(?adminLabel)="en") }}
 OPTIONAL {{ ?item wdt:P17 ?country . ?country rdfs:label ?countryLabel FILTER(LANG(?countryLabel)="en") }}
 SERVICE wikibase:label {{ bd:serviceParam wikibase:language "en". }}
 }}
 LIMIT {SPARQL_LIMIT} OFFSET {offset}
 """
 res = run_sparql(query)
 if not res:
 print(f"Sınıf {cls} için veri çekilemedi. Sonraki sınıfa geçiliyor.")
 break.
 bindings = res.get("results", {}).get("bindings", [])
 if not bindings:
 print(f"Sınıf {cls} için kayıt kalmadı. Sonraki sınıfa geçiliyor.")
 break.

 for b in bindings:
 name = b.get("itemLabel", {}).get("value")
 desc = b.get("desc", {}).get("value", "")
 inst = b.get("instanceLabel", {}).get("value") if b.get("instanceLabel") else None.
 admin = b.get("adminLabel", {}).get("value") if b.get("adminLabel") else None.
 coord_literal = b.get("coord", {}).get("value")
 country = b.get("countryLabel", {}).get("value") if b.get("countryLabel") else None.

 parsed = parse_coord(coord_literal)
 if not parsed or not name:
 continue.
 lat, lon = parsed.
 city = admin if admin else reverse_geocode_city(geolocator, lat, lon)

 rec = {
 "name": name,
 "city": city or (country or "Unknown"),
 "description": desc or f"{name} ({inst or 'Attraction'}) - photo spot.",
 "type": normalize_type(inst),
 "coordinates": {"lat": round(lat,6), "lon": round(lon,6)}
 }
 spots.append(rec)

 offset += SPARQL_LIMIT.
 time.sleep(CHUNK_DELAY)
 if len(bindings) < SPARQL_LIMIT:
 break.

 print(f"Collected records before dedupe: {len(spots)}")
 spots = dedupe_places(spots)
 print(f"Records after dedupe: {len(spots)}")

 with open(OUTPUT_FILE, "w", encoding="utf-8") as f:
 json.dump(spots, f, ensure_ascii=False, indent=2)

 print(f"Saved {len(spots)} records to {OUTPUT_FILE}")
 if len(spots) < MIN_RECORDS:
 print(f"UYARI: Toplam kayıt {len(spots)}; hedef {MIN_RECORDS}. Daha fazla kaynak/şehir sorgulaması gerekebilir.")
 else:
 print("Hedef kayıt sayısına ulaşıldı veya aşıldı.")

if __name__ == "__main__":
 main()

Teşekkürler.
 
504 ve 403 hataları genellikle sunucu yükü veya IP limiti kaynaklı. Sorgu limitini düşür beklemeyi rastgele jitter yap sorguyu optimize et ve veriyi parça parça kaydet.
 
504 ve 403 hataları genellikle sunucu yükü veya IP limiti kaynaklı. Sorgu limitini düşür beklemeyi rastgele jitter yap sorguyu optimize et ve veriyi parça parça kaydet.

Teşekkürler. Deneyip geri dönüş yapacağım.

Hocam sorguyu iki aşamaya böldüm. İlk aşamada sadece item ID + koordinat çekiyorum. Daha sonra bu ID’leri küçük 20’şerlik gruplar halinde yeniden sorgulayıp dönüş yapacağım size.

Biraz vakit alabilir. Size kesin dönüşü yaparım tekrardan.

Projeyi rafa kaldırdım zamansızlıktan ötürü. Yeni yılda üstüne giderim belki.
 
Son düzenleme:

Technopat Haberler

Yeni konular

Geri
Yukarı