Kümeler

Küme, kaydedilmiş bir web sitesi listesidir. API üzerinden bir aramadan ya da kendi listenizden küme oluşturabilir, kümeleri birleştirebilir ve bir kümedeki her sitenin sayfa kaynağından değerler çıkarabilirsiniz: iletişim bilgileri, sosyal medya profilleri, analiz ve etiket kimlikleri, bir düzenli ifadenin eşleşebileceği her şey.

Uç noktalar

Uç noktaYöntemNe yapar
/v1/clustersGETHesabın kümeleri, en yenisi önce: kimlik, ad, alan adı sayısı, oluşturulma zamanı.
/v1/clustersPOSTBir aramadan (query) ya da bir listeden (domains) küme oluşturur; name isteğe bağlıdır.
/v1/clusters/{id}GETKüme ve alan adlarından bir sayfa: page, per_page (en fazla 10.000), her satıra bir alan adı için format=txt.
/v1/clusters/combinePOSTMevcut kümelerden yeni bir küme: operation and, or ya da diff, clusters ise kimlik listesi.
/v1/clusters/{id}/extractGET, POSTpresets ve regex ile parça parça değer çıkarır: offset, limit (çağrı başına en fazla 1.000 site), format json, xml ya da csv.
/v1/clusters/presetsGETHazır ifadeler ve kalıpları.
/v1/clusters/{id}/renamePOSTYeni name.
/v1/clusters/{id}/deletePOSTKümeyi kalıcı olarak siler.

Bir kümeyi değiştiren her işlem JSON gövdeli bir POST isteğidir; PUT ya da DELETE yoktur, böylece arama yapabilen her istemci kümeleri de yönetebilir. Token, hız sınırı ve hata biçimi aramadakiyle aynıdır; /v1/account yanıtındaki küme sayıları kaç kümeniz olduğunu ve bugün kaç veri çıkarma puanınızın kaldığını gösterir.

Küme oluşturma

Bir aramadan: sorgunun sonuçları, planınızın arama başına satır sınırına kadar, kümeye dönüşür. /v1/search gibi bir arama harcar.

curl https://api.publicwww.com/v1/clusters \
     -H "Authorization: Bearer $PUBLICWWW_KEY" \
     -H "Content-Type: application/json" \
     -d '{"query": "\"googletagmanager.com/gtm.js\"", "name": "GTM sites"}'

{"id": 7, "name": "GTM sites", "size": 100000, "created": "2026-10-10T20:21:30Z",
 "query": "\"googletagmanager.com/gtm.js\"", "total": 2412577, "index_complete": true}

Bir listeden: JSON dizisi olarak ya da her satırda bir tane olacak şekilde alan adları veya URL'ler. Yalnızca dizinde bulunan siteler tutulur: submitted kaç tane gönderdiğinizi, size kaçının kümeye girdiğini gösterir. Liste hiçbir şey harcamaz.

curl https://api.publicwww.com/v1/clusters \
     -H "Authorization: Bearer $PUBLICWWW_KEY" \
     -H "Content-Type: application/json" \
     -d '{"domains": ["example.com", "https://www.example.org/about"], "name": "Prospects"}'

Plan, bir kümedeki alan adı sayısını sınırlar; bir hesap da en fazla 100 küme saklar. Zaten 100 küme varken yenisini oluşturmak 409 cluster_limit yanıtını alır: sizin yerinize hiçbir şey silinmez, önce artık gerekmeyen kümeleri silin.

Kümeleri birleştirme

curl https://api.publicwww.com/v1/clusters/combine \
     -H "Authorization: Bearer $PUBLICWWW_KEY" \
     -H "Content-Type: application/json" \
     -d '{"operation": "diff", "clusters": [7, 3], "name": "GTM, not yet contacted"}'

and tüm kümelerde bulunan alan adlarını, or herhangi birinde bulunanları, diff ise birinci kümede olup ikincide olmayanları tutar (bu durumda tam iki küme verilir). Birleştirme hiçbir şey harcamaz.

Veri çıkarma

Veri çıkarma, kümedeki her sitenin dizine eklenmiş sayfalarını okur ve ifadelerin yakaladıklarını her ifade için bir sütun olarak döndürür. En kolay yol bir hazır kalıptır:

Hazır kalıpNeyi çıkarır
emailmailto: bağlantılarındaki adresler
phonetel: bağlantılarındaki numaralar
whatsapp, telegram, skypeKendi bağlantılarından WhatsApp numaraları, Telegram kullanıcı adları, Skype adları
facebook, instagram, twitter, linkedinSitenin sosyal medya profillerine bağlantılar (twitter x.com'u, linkedin şirket sayfalarını da alır)
gtm, ga4, uaGoogle Tag Manager kapsayıcısı, Google Analytics 4 ve Universal Analytics kimlikleri
hotjarHotjar site kimliği
adsenseAdSense yayıncı kimliği
bitcoinbitcoin: ödeme bağlantılarındaki adresler

Ya da kendi ifadenizi yazın: eğik çizgiler (ya da dikey çizgiler) arasında, isteğe bağlı i, m, s, u bayraklarıyla, en fazla 200 karakterlik bir düzenli ifade. Değer, ilk yakalama grubudur; snipexp: ile aynı kural. Hazır kalıplar dahil, çağrı başına en fazla on ifade kullanılabilir.

curl https://api.publicwww.com/v1/clusters/7/extract \
     -H "Authorization: Bearer $PUBLICWWW_KEY" \
     -H "Content-Type: application/json" \
     -d '{"presets": ["gtm", "email"], "regex": ["/data-site-id=\"([0-9]+)\"/i"], "limit": 1000}'

{
  "cluster": 7, "name": "GTM sites", "size": 100000,
  "offset": 0, "scanned": 1000, "in_index": 1000, "with_matches": 941,
  "next_offset": 1000,
  "regex": ["/(GTM-[A-Z0-9]{4,10})\\b/", "/mailto:(...)/i", "/data-site-id=\"([0-9]+)\"/i"],
  "points_used": 1834.2, "points_left": 98165,
  "rows": [
    { "domain": "example.com", "values": [["GTM-AB12CD"], ["info@example.com"], []], "matches": 2 }
  ]
}

Parça parça. Bir çağrı, offset değerinden başlayarak kümenin en fazla 1.000 sitesini işler. Yanıt null dönene kadar, offset değerini next_offset yaparak yeniden çağırın. Hiçbir şeyin eşleşmediği siteler yanıta alınmaz; skip_empty=0 bunları da listeler. format=csv her site için bir satır verir (önce alan adı, sonra her ifade için bir sütun); sonraki offset X-Next-Offset başlığındadır.

Puanlar. Veri çıkarma, planınızın günlük veri çıkarma hakkından harcar: dizinde bulunan her site, üzerinde bulunan değer sayısı kadar puan harcar; hiç değer bulunmazsa 0,1. Puanlar bittiğinde çağrı erken durur ve "stopped": "extract_quota_exceeded" ile o ana kadar bulduklarını döndürür; hiç puan kalmamışken yapılan çağrı 429 extract_quota_exceeded alır. Bir ifadeyi önce küçük bir limit ile deneyin; bkz. küme sınırları.

Kodla bütün bir küme

Bir aramadan küme oluşturun ve her sitenin çıkarılan değerlerini bir CSV dosyasına yazın. İstemci kütüphaneleri aynısını hazır fonksiyonlar ve bir komut satırı aracı olarak sunar.

Python

import csv, os, time, requests

KEY  = os.environ["PUBLICWWW_KEY"]
BASE = "https://api.publicwww.com"
H    = {"Authorization": "Bearer " + KEY}

def call(method, path, body=None):
    while True:
        r = requests.request(method, BASE + path, headers=H, json=body)
        if r.status_code == 429 and r.json()["error"]["code"] == "too_many_requests":
            time.sleep(int(r.headers.get("Retry-After", 30)))
            continue
        r.raise_for_status()
        return r.json()

cluster = call("POST", "/v1/clusters", {"query": '"googletagmanager.com/gtm.js"'})
offset = 0
with open("extract.csv", "w", newline="") as f:
    out = csv.writer(f)
    while offset is not None:
        part = call("POST", "/v1/clusters/%d/extract" % cluster["id"],
                    {"presets": ["gtm", "email"], "offset": offset})
        for row in part["rows"]:
            out.writerow([row["domain"]] + [" ".join(v) for v in row["values"]])
        offset = part["next_offset"]

JavaScript (Node 18+)

const BASE = "https://api.publicwww.com";
const H = { Authorization: "Bearer " + process.env.PUBLICWWW_KEY,
            "Content-Type": "application/json" };

async function call(method, path, body) {
  for (;;) {
    const r = await fetch(BASE + path, { method, headers: H,
                                         body: body && JSON.stringify(body) });
    const data = await r.json();
    if (r.status === 429 && data.error.code === "too_many_requests") {
      await new Promise(ok => setTimeout(ok, 1000 * (r.headers.get("Retry-After") || 30)));
      continue;
    }
    if (!r.ok) throw new Error(data.error.message);
    return data;
  }
}

const cluster = await call("POST", "/v1/clusters", { query: '"hotjar.com"' });
for (let offset = 0; offset !== null; ) {
  const part = await call("POST", `/v1/clusters/${cluster.id}/extract`,
                          { presets: ["hotjar", "email"], offset });
  for (const row of part.rows) console.log(row.domain, row.values.map(v => v.join(" ")).join(";"));
  offset = part.next_offset;
}

PHP

<?php
function call ($method, $path, $body = null) {
    $ch = curl_init ("https://api.publicwww.com" . $path);
    curl_setopt_array ($ch, [
        CURLOPT_CUSTOMREQUEST  => $method,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => ["Authorization: Bearer " . getenv ("PUBLICWWW_KEY"),
                                   "Content-Type: application/json"],
        CURLOPT_POSTFIELDS     => $body === null ? null : json_encode ($body),
    ]);
    $data = json_decode (curl_exec ($ch), true);
    if (isset ($data ["error"])) throw new Exception ($data ["error"]["message"]);
    return $data;
}

$cluster = call ("POST", "/v1/clusters", ["query" => '"jquery.min.js"']);
$out = fopen ("extract.csv", "w");
for ($offset = 0; $offset !== null; ) {
    $part = call ("POST", "/v1/clusters/" . $cluster ["id"] . "/extract",
                  ["presets" => ["email", "phone"], "offset" => $offset]);
    foreach ($part ["rows"] as $row)
        fputcsv ($out, array_merge ([$row ["domain"]], array_map (fn ($v) => join (" ", $v), $row ["values"])));
    $offset = $part ["next_offset"];
}

Go

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"strings"
)

func call(method, path string, body, out any) error {
	b, _ := json.Marshal(body)
	req, _ := http.NewRequest(method, "https://api.publicwww.com"+path, bytes.NewReader(b))
	req.Header.Set("Authorization", "Bearer "+os.Getenv("PUBLICWWW_KEY"))
	req.Header.Set("Content-Type", "application/json")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode >= 300 {
		return fmt.Errorf("publicwww: %s", resp.Status)
	}
	return json.NewDecoder(resp.Body).Decode(out)
}

func main() {
	var cluster struct{ ID int `json:"id"` }
	if err := call("POST", "/v1/clusters", map[string]any{"query": `"googletagmanager.com/gtm.js"`}, &cluster); err != nil {
		panic(err)
	}
	for offset := 0; ; {
		var part struct {
			Rows []struct {
				Domain string     `json:"domain"`
				Values [][]string `json:"values"`
			} `json:"rows"`
			NextOffset *int `json:"next_offset"`
		}
		path := fmt.Sprintf("/v1/clusters/%d/extract", cluster.ID)
		if err := call("POST", path, map[string]any{"presets": []string{"gtm", "ga4"}, "offset": offset}, &part); err != nil {
			panic(err)
		}
		for _, r := range part.Rows {
			cells := []string{r.Domain}
			for _, v := range r.Values {
				cells = append(cells, strings.Join(v, " "))
			}
			fmt.Println(strings.Join(cells, ";"))
		}
		if part.NextOffset == nil {
			break
		}
		offset = *part.NextOffset
	}
}

Ruby

require "json"
require "net/http"

def call(path, body)
  uri = URI("https://api.publicwww.com" + path)
  req = Net::HTTP::Post.new(uri, "Authorization" => "Bearer #{ENV.fetch('PUBLICWWW_KEY')}",
                                  "Content-Type" => "application/json")
  req.body = body.to_json
  res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
  data = JSON.parse(res.body)
  raise data["error"]["message"] if data["error"]
  data
end

cluster = call("/v1/clusters", { query: '"hotjar.com"' })
offset = 0
while offset
  part = call("/v1/clusters/#{cluster['id']}/extract", { presets: %w[hotjar email], offset: offset })
  part["rows"].each { |r| puts [r["domain"], *r["values"].map { |v| v.join(" ") }].join(";") }
  offset = part["next_offset"]
end

Hatalar

Durum ve kodAnlamı
404 cluster_not_foundBu hesapta bu kimliğe sahip bir küme yok.
409 cluster_limitHesapta zaten 100 küme var.
400 invalid_regexBir ifade, eğik ya da dikey çizgiler arasına yazılmış ve en fazla 200 karakterlik bir PCRE değil.
400 unknown_presetBöyle bir hazır kalıp yok; yanıt mevcut olanları listeler.
400 missing_source, ambiguous_sourceKüme oluşturmak için query ya da domains parametrelerinden yalnızca biri gerekir.
429 extract_quota_exceededBugünün veri çıkarma puanları tükendi.

Tam liste hatalar sayfasında ve API'nin https://api.publicwww.com/ adresindeki kendi tanımında yer alır. Bir yapay zeka asistanında aynı işlemler MCP araçları olarak sunulur.

Sonraki Kod örnekleri