# -*- coding: utf-8 -*-
"""One polite homepage request per Greek domain, for a technical audit.
Sample frame: Tranco top-1M list PYG5J (2026-08-06), filtered to .gr.
One GET per domain, identifying user agent, 20s timeout, no retries, no crawling
beyond the homepage. Records response metadata and the signals needed to decide
whether a site is an e-shop and how it is built.
"""
import csv
import io
import json
import os
import re
import socket
import ssl
import sys
import time
from concurrent.futures import ThreadPoolExecutor
import urllib.request
HERE = os.path.dirname(os.path.abspath(__file__))
OUT = os.path.join(HERE, 'gr_raw2.jsonl')
N = int(sys.argv[1]) if len(sys.argv) > 1 else 700
UA = ('Mozilla/5.0 (compatible; broikos-research/1.0; +https://broikos.gr/research/; '
'one homepage request per domain for a published technical audit)')
CTX = ssl.create_default_context()
CTX.check_hostname = False
CTX.verify_mode = ssl.CERT_NONE # measure TLS separately; do not fail the fetch
# Signatures must be specific. An earlier version used `mage-`, which matches
# `image-` and mislabelled 64% of the sample as Magento.
SHOP = [
(r'woocommerce|wc-ajax|wp-content/plugins/woocommerce', 'WooCommerce'),
(r'cdn\.shopify\.com|shopify\.com/s/files|myshopify', 'Shopify'),
(r'magento|mage/requirejs|magento_|/static/version\d', 'Magento'),
(r'prestashop', 'PrestaShop'),
(r'opencart|route=common', 'OpenCart'),
(r'cs-cart', 'CS-Cart'),
(r'wixstatic|_partials/wix', 'Wix'),
(r'bigcommerce', 'BigCommerce'),
(r'squarespace', 'Squarespace'),
]
CMS = [(r'wp-content|wp-includes', 'WordPress'), (r'drupal', 'Drupal'),
(r'joomla', 'Joomla'), (r'/_next/', 'Next.js'), (r'nuxt', 'Nuxt')]
def tls_info(host):
try:
c = ssl.create_default_context()
with socket.create_connection((host, 443), timeout=10) as s:
with c.wrap_socket(s, server_hostname=host) as ss:
cert = ss.getpeercert()
return ss.version(), cert.get('notAfter', ''), True
except ssl.SSLError as e:
return None, str(e)[:80], False
except Exception:
return None, '', False
def fetch(row):
rank, domain = row
rec = {'rank': rank, 'domain': domain}
url = 'https://' + domain
t0 = time.time()
try:
req = urllib.request.Request(url, headers={'User-Agent': UA,
'Accept': 'text/html,*/*',
'Accept-Encoding': 'gzip, deflate'})
r = urllib.request.urlopen(req, timeout=20, context=CTX)
raw = r.read(400_000)
rec['status'] = r.status
rec['elapsed_ms'] = int((time.time() - t0) * 1000)
rec['final_url'] = r.geturl()
h = {k.lower(): v for k, v in r.headers.items()}
rec['headers'] = {k: h.get(k, '') for k in
('server', 'content-encoding', 'strict-transport-security',
'content-security-policy', 'x-frame-options',
'x-content-type-options', 'referrer-policy',
'permissions-policy', 'set-cookie', 'content-type')}
rec['bytes'] = len(raw)
enc = h.get('content-encoding', '')
if 'gzip' in enc:
import gzip
try:
raw = gzip.decompress(raw)
except Exception:
pass
elif 'deflate' in enc:
import zlib
try:
raw = zlib.decompress(raw)
except Exception:
pass
html = raw.decode('utf-8', 'replace')
rec['html_bytes'] = len(html)
low = html.lower()
rec['platform'] = next((n for p, n in SHOP if re.search(p, low)), '')
rec['platform_all'] = [n for p, n in SHOP if re.search(p, low)]
rec['cms_all'] = [n for p, n in CMS if re.search(p, low)]
rec['sig'] = {k: bool(re.search(v, low)) for k, v in {
'woocommerce': r'woocommerce', 'shopify': r'myshopify|cdn\.shopify',
'magento_word': r'magento', 'static_version': r'/static/version\d',
'mage_dash': r'mage-', 'image_dash': r'image-',
'prestashop': r'prestashop', 'opencart': r'opencart',
}.items()}
rec['cms'] = next((n for p, n in CMS if re.search(p, low)), '')
rec['jsonld_product'] = bool(re.search(r'"@type"\s*:\s*"(product|offer)"', low))
rec['cart_signal'] = bool(re.search(r'καλάθι|add[-_ ]?to[-_ ]?cart|/cart|/basket|'
r'checkout|προϊόν|αγορά', low))
rec['viewport'] = bool(re.search(r']+name=["\']viewport', low))
rec['lang_attr'] = bool(re.search(r']+lang=', low))
rec['imgs'] = len(re.findall(r']*\balt=', low))
rec['inputs'] = len(re.findall(r']*>(.*?)', html, re.S | re.I).group(1).strip()[:120]
if re.search(r'