#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""抓取宝锐官网产品目录（产品详情页 + 说明书 PDF 链接 + 页面正文）。

产出：
  数据/biori_official_catalog.json  —— 每个产品的：品名 / 官网分类 / 详情页URL / 说明书文件URL / 说明书文件名(含货号) / 页面正文
  资料/说明书/<文件名>.pdf          —— 说明书 PDF 本体（供后续抽取体系配制表与反应程序）

说明：只读抓取官网公开页面，不改动任何本地数据；请求间隔 0.6s，礼貌爬取。
"""
import json
import os
import re
import time
import urllib.request

from lxml import html as LH

BASE = 'https://www.biori.com'
OUT_JSON = '/Users/liuxinyuan/Desktop/Hermes输出-工作类/数据/biori_official_catalog.json'
PDF_DIR = '/Users/liuxinyuan/Desktop/Hermes输出-工作类/资料/说明书'
UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/122 Safari/537.36'


def get(url, timeout=30):
    req = urllib.request.Request(url, headers={'User-Agent': UA})
    with urllib.request.urlopen(req, timeout=timeout) as r:
        return r.read()


def txt(el):
    return re.sub(r'\s+', ' ', el.text_content()).strip()


def parse_list_page(html_bytes):
    root = LH.fromstring(html_bytes)
    out = []
    for a in root.xpath('//a[@href]'):
        h = a.get('href') or ''
        m = re.match(r'^https?://www\.biori\.com/product/(\d+)$', h)
        if m:
            name = txt(a)
            out.append({'id': m.group(1), 'url': h, 'name': name})
    return out


def parse_detail(url):
    b = get(url)
    root = LH.fromstring(b)
    title = (root.xpath('//title/text()') or [''])[0].strip()
    name = re.sub(r'-珠海宝锐生物科技有限公司.*$', '', title).strip()
    body = txt(root)
    # 面包屑 / 分类（取产品标题里的【】标注，以及导航中的诊断原料子类）
    cat = ''
    m = re.search(r'【([^】]{2,20})】', name)
    if m:
        cat = m.group(1)
    # 说明书与资料链接
    docs = []
    for a in root.xpath('//a[@href]'):
        h = (a.get('href') or '').strip()
        if re.search(r'\.(pdf|docx?|zip|xlsx?)$', h, re.I) or 'oss' in h and 'uploads' in h:
            docs.append({'file_url': h, 'label': txt(a)[:40]})
    # 页面里的货号线索
    skus = sorted(set(re.findall(r'\b(?:BR\d)?[A-Z]{1,4}\d{2,5}(?:-\d{2,6})?(?:-[A-Z0-9]{1,8})?\b', name + ' ' + body)))[:12]
    return {'官网品名': name, '官网标注分类': cat, '详情页': url,
            '说明书与资料': docs, '货号线索': skus, '正文': body[:4000]}


def main():
    os.makedirs(PDF_DIR, exist_ok=True)
    seen, products = set(), []
    for page in range(1, 22):
        url = '%s/products?lang=&page=%d' % (BASE, page)
        try:
            items = parse_list_page(get(url))
        except Exception as e:
            print('  列表页 %d 失败: %s' % (page, e))
            continue
        new = [p for p in items if p['id'] not in seen]
        for p in new:
            seen.add(p['id'])
        products.extend(new)
        print('第 %2d 页：+%d（累计 %d）' % (page, len(new), len(products)))
        if not items:
            break
        time.sleep(0.6)
    print('共发现产品 %d 个，开始抓详情…' % len(products))
    for i, p in enumerate(products, 1):
        try:
            d = parse_detail(p['url'])
            p.update(d)
        except Exception as e:
            p['错误'] = '%s: %s' % (type(e).__name__, e)
        if i % 20 == 0:
            print('  详情 %d/%d' % (i, len(products)))
        time.sleep(0.6)
    json.dump({'抓取时间': time.strftime('%Y-%m-%d %H:%M'), '来源': BASE + '/products',
               '产品数': len(products), 'items': products},
              open(OUT_JSON, 'w', encoding='utf-8'), ensure_ascii=False, indent=1)
    with_doc = sum(1 for p in products if p.get('说明书与资料'))
    print('→ %s ｜ 产品 %d ｜ 带资料链接 %d' % (OUT_JSON, len(products), with_doc))


if __name__ == '__main__':
    main()
