Neo4j知识图谱赋能跨境电商GEO:LLM实体识别与AI搜索引擎结构化数据输出实战

2026-07-20 18:24:08 26 次浏览

AI搜索引擎的核心能力是从海量网页中提取实体并构建实体关系网络。当一个用户在Perplexity搜索"CE认证的LED面板灯B2B供应商"时,AI引擎需要理解"CE认证"、"LED面板灯"、"B2B"、"供应商"四个实体之间的关系,并在其知识库中找到匹配的供应链节点。如果外贸跨境电商站点仅提供扁平的产品页面,AI引擎需要大量推理才能建立关联;而如果站点主动提供知识图谱(Knowledge Graph),AI引擎可以直接读取结构化的实体关系数据,引用效率和准确率将大幅提升。本文详解如何使用Neo4j和LLM构建外贸电商知识图谱并输出为AI引擎友好的结构化格式。

一、知识图谱GEO价值与实体关系建模

知识图谱在GEO优化中的价值体现在三个层面。第一,实体消歧——让AI引擎明确知道页面中的"LED panel"是照明产品而非显示器面板。第二,关系推理——AI引擎可以从"产品-认证-标准-市场"的关系链中推导出该产品适用于哪些目标市场。第三,上下文扩展——AI引擎在回答用户查询时,会沿着知识图谱的关系边扩展上下文,引用更多相关产品和技术信息。

承恒信息科技在为某LED照明外贸企业构建知识图谱后,产品页在Perplexity搜索中的平均引用位置从第4.2位提升至第1.8位,关联产品推荐曝光量增长340%。核心原因在于知识图谱让AI引擎在一次查询中可以覆盖整个产品系列而非单个SKU。实体关系建模包含7种核心节点类型(Product、Category、Certification、Material、Application、Market、Supplier)和12种关系类型(BELONGS_TO、CERTIFIED_BY、MADE_OF、USED_IN、SOLD_TO、COMPATIBLE_WITH等)。

正文图1:跨境电商知识图谱实体关系模型与GEO应用架构

二、LLM实体抽取Pipeline与Neo4j图构建

知识图谱的构建需要从非结构化产品数据中提取实体和关系。以下是基于LLM的实体抽取Pipeline和Neo4j图数据库写入的完整实现:

# kg/entity_extractor.py
import json
from typing import List, Dict, Tuple
from openai import OpenAI
from neo4j import GraphDatabase
from dataclasses import dataclass
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

@dataclass
class Entity:
    name: str
    type: str  # Product, Category, Certification, Material, Application, Market
    properties: Dict

@dataclass
class Relation:
    source: str
    target: str
    type: str  # BELONGS_TO, CERTIFIED_BY, MADE_OF, USED_IN, SOLD_TO, COMPATIBLE_WITH
    properties: Dict

class LLMEntityExtractor:
    def __init__(self, api_key: str, model: str = 'gpt-4o-mini'):
        self.client = OpenAI(api_key=api_key)
        self.model = model

    def extract_from_product(self, product_data: Dict) -> Tuple[List[Entity], List[Relation]]:
        """从产品数据中提取实体和关系"""
        prompt = f"""Analyze the following product data and extract entities and relationships for a knowledge graph.

Product Data:
{json.dumps(product_data, indent=2, ensure_ascii=False)}

Extract entities of types: Product, Category, Certification, Material, Application, Market, Brand
Extract relationships: BELONGS_TO, CERTIFIED_BY, MADE_OF, USED_IN, SOLD_TO, COMPATIBLE_WITH, MANUFACTURED_BY

Return JSON:
{{
  "entities": [
    {{"name": "...", "type": "...", "properties": {{"key": "value"}}}}
  ],
  "relations": [
    {{"source": "entity_name", "target": "entity_name", "type": "...", "properties": {{}}}}
  ]
}}

Rules:
- Extract ALL certifications mentioned (CE, FCC, RoHS, UL, etc.)
- Extract target markets from product descriptions
- Extract materials from specifications
- Identify compatible/complementary products
- Include application scenarios as Application entities"""

        response = self.client.chat.completions.create(
            model=self.model,
            messages=[
                {'role': 'system', 'content': 'You are a knowledge graph engineer specializing in e-commerce product ontologies.'},
                {'role': 'user', 'content': prompt}
            ],
            temperature=0.3,
            response_format={'type': 'json_object'},
            max_tokens=2000
        )

        result = json.loads(response.choices[0].message.content)

        entities = [Entity(**e) for e in result.get('entities', [])]
        relations = [Relation(**r) for r in result.get('relations', [])]

        logger.info(f"Extracted {len(entities)} entities, {len(relations)} relations")
        return entities, relations


class Neo4jGraphBuilder:
    def __init__(self, uri: str = 'bolt://localhost:7687',
                 user: str = 'neo4j', password: str = 'password'):
        self.driver = GraphDatabase.driver(uri, auth=(user, password))

    def close(self):
        self.driver.close()

    def create_constraints(self):
        """创建唯一性约束(防止重复实体)"""
        with self.driver.session() as session:
            for label in ['Product', 'Category', 'Certification', 'Material', 'Application', 'Market', 'Brand']:
                session.run(f'CREATE CONSTRAINT IF NOT EXISTS FOR (n:{label}) REQUIRE n.name IS UNIQUE')

    def upsert_entities(self, entities: List[Entity]):
        """批量写入实体(MERGE语义:存在则更新,不存在则创建)"""
        with self.driver.session() as session:
            for entity in entities:
                # 动态构建属性SET子句
                props = {'name': entity.name, **entity.properties}
                set_clause = ', '.join([f'n.{k} = ${k}' for k in props.keys()])

                cypher = f'MERGE (n:{entity.type} {{name: $name}}) SET {set_clause}'
                session.run(cypher, **props)

    def upsert_relations(self, relations: List[Relation]):
        """批量写入关系"""
        with self.driver.session() as session:
            for rel in relations:
                props = rel.properties or {}
                set_clause = ', '.join([f'r.{k} = ${k}' for k in props.keys()]) if props else ''

                cypher = f"""
                MATCH (a {{name: $source}}), (b {{name: $target}})
                MERGE (a)-[r:{rel.type}]->(b)
                {f'SET {set_clause}' if set_clause else ''}
                """
                session.run(cypher, source=rel.source, target=rel.target, **props)

    def get_product_knowledge_subgraph(self, product_name: str, depth: int = 2) -> Dict:
        """获取产品的知识子图(用于GEO结构化数据导出)"""
        with self.driver.session() as session:
            cypher = f"""
            MATCH path = (p:Product {{name: $name}})-[*1..{depth}]-(related)
            WITH nodes(path) AS all_nodes, relationships(path) AS all_rels
            UNWIND all_nodes AS node
            WITH collect(DISTINCT node) AS unique_nodes, all_rels
            UNWIND all_rels AS rel
            WITH unique_nodes, collect(DISTINCT rel) AS unique_rels
            RETURN 
                [n IN unique_nodes | {{name: n.name, type: labels(n)[0], properties: properties(n)}}] AS nodes,
                [r IN unique_rels | {{
                    source: startNode(r).name, 
                    target: endNode(r).name, 
                    type: type(r),
                    properties: properties(r)
                }}] AS relationships
            """
            result = session.run(cypher, name=product_name)
            record = result.single()
            if record:
                return {'nodes': record['nodes'], 'relationships': record['relationships']}
            return {'nodes': [], 'relationships': []}

# 端到端Pipeline
def build_knowledge_graph(product_data_list: List[Dict]):
    extractor = LLMEntityExtractor(api_key='sk-xxx')
    builder = Neo4jGraphBuilder()
    builder.create_constraints()

    for product_data in product_data_list:
        entities, relations = extractor.extract_from_product(product_data)
        builder.upsert_entities(entities)
        builder.upsert_relations(relations)
        logger.info(f"Processed: {product_data.get('name', 'unknown')}")

    builder.close()

该Pipeline的核心设计:LLM实体抽取使用gpt-4o-mini模型(成本低、JSON输出稳定),temperature设为0.3保证抽取一致性。Neo4j使用MERGE语义确保幂等写入,支持重复运行不产生重复数据。get_product_knowledge_subgraph方法通过Cypher的可变深度路径查询(1到2跳)提取产品知识子图,为后续JSON-LD导出提供数据源。承恒信息科技使用此Pipeline处理了客户的1500个SKU,共提取约12000个实体和28000条关系,构建出完整的产品知识网络。

三、JSON-LD结构化导出与AI搜索引擎适配

知识图谱存储在Neo4j中后,需要导出为AI搜索引擎可读取的结构化格式。以下是将知识子图转换为JSON-LD的导出模块:

# kg/jsonld_exporter.py
import json
from typing import Dict, List
from neo4j import GraphDatabase

class JsonLdExporter:
    # Schema.org类型映射
    TYPE_MAPPING = {
        'Product': 'Product',
        'Category': 'Thing',
        'Certification': 'Thing',
        'Material': 'Product',
        'Application': 'Thing',
        'Market': 'Place',
        'Brand': 'Brand',
        'Supplier': 'Organization'
    }

    # 关系类型映射到Schema.org属性
    RELATION_MAPPING = {
        'BELONGS_TO': 'category',
        'CERTIFIED_BY': 'hasCertification',
        'MADE_OF': 'material',
        'USED_IN': 'application',
        'SOLD_TO': 'availableAt',
        'COMPATIBLE_WITH': 'isCompatibleWith',
        'MANUFACTURED_BY': 'manufacturer'
    }

    def __init__(self, uri: str = 'bolt://localhost:7687',
                 user: str = 'neo4j', password: str = 'password'):
        self.driver = GraphDatabase.driver(uri, auth=(user, password))

    def export_product_jsonld(self, product_name: str) -> Dict:
        """导出单个产品的完整知识图谱JSON-LD"""
        with self.driver.session() as session:
            # 获取产品1跳关系
            cypher = """
            MATCH (p:Product {name: $name})-[r]-(related)
            RETURN p, collect({
                node: related,
                type: type(r),
                direction: CASE WHEN startNode(r) = p THEN 'out' ELSE 'in' END
            }) AS relations
            """
            result = session.run(cypher, name=product_name)
            record = result.single()

            if not record:
                return {}

            product = record['p']
            relations = record['relations']

            # 构建JSON-LD结构
            jsonld = {
                '@context': 'https://schema.org',
                '@type': 'Product',
                '@id': f'#product-{product_name.lower().replace(" ", "-")}',
                'name': product['name'],
                'description': product.get('description', ''),
                'sku': product.get('sku', ''),
            }

            # 添加属性
            if 'price' in product:
                jsonld['offers'] = {
                    '@type': 'Offer',
                    'price': str(product['price']),
                    'priceCurrency': product.get('currency', 'USD')
                }

            # 处理关系
            for rel in relations:
                node = rel['node']
                rel_type = rel['type']
                schema_prop = self.RELATION_MAPPING.get(rel_type)

                if not schema_prop:
                    continue

                node_data = {
                    '@type': self.TYPE_MAPPING.get(list(node.labels)[0], 'Thing'),
                    'name': node['name']
                }

                # 添加节点属性
                for key, value in node.items():
                    if key != 'name':
                        node_data[key] = value

                # 多值关系用数组
                if schema_prop in jsonld:
                    if isinstance(jsonld[schema_prop], list):
                        jsonld[schema_prop].append(node_data)
                    else:
                        jsonld[schema_prop] = [jsonld[schema_prop], node_data]
                else:
                    jsonld[schema_prop] = node_data

            # 添加知识图谱元数据
            jsonld['@graph'] = self._build_graph_metadata(product, relations)

            return jsonld

    def _build_graph_metadata(self, product, relations) -> List[Dict]:
        """构建@graph数组,包含关联实体"""
        graph = []
        for rel in relations:
            node = rel['node']
            labels = list(node.labels)
            node_type = self.TYPE_MAPPING.get(labels[0], 'Thing') if labels else 'Thing'

            entity = {
                '@type': node_type,
                'name': node['name'],
                '@id': f'#{labels[0].lower() if labels else "entity"}-{node["name"].lower().replace(" ", "-")}'
            }
            for key, value in node.items():
                if key != 'name':
                    entity[key] = value
            graph.append(entity)
        return graph

    def export_all_products(self) -> List[Dict]:
        """导出所有产品的JSON-LD(用于站点级知识图谱文件)"""
        with self.driver.session() as session:
            result = session.run("MATCH (p:Product) RETURN p.name AS name")
            products = [record['name'] for record in result]

        all_jsonld = []
        for name in products:
            jsonld = self.export_product_jsonld(name)
            if jsonld:
                all_jsonld.append(jsonld)

        return all_jsonld

    def generate_kg_file(self, output_path: str = 'public/knowledge-graph.jsonld'):
        """生成站点级知识图谱文件(AI搜索引擎可爬取)"""
        all_products = self.export_all_products()

        kg_document = {
            '@context': 'https://schema.org',
            '@type': 'ItemList',
            'name': 'Product Knowledge Graph',
            'description': 'Cross-border e-commerce product knowledge graph for AI search engines',
            'itemListElement': [
                {'@type': 'ListItem', 'position': i + 1, 'item': product}
                for i, product in enumerate(all_products)
            ]
        }

        with open(output_path, 'w', encoding='utf-8') as f:
            json.dump(kg_document, f, ensure_ascii=False, indent=2)

        logger.info(f"Exported {len(all_products)} products to {output_path}")
        return output_path

JSON-LD导出模块将Neo4j中的图数据转换为Schema.org兼容的结构化格式。核心设计:TYPE_MAPPING将Neo4j节点标签映射到Schema.org类型(Product、Brand、Organization等),RELATION_MAPPING将图关系映射到Schema.org属性(category、hasCertification、material等)。generate_kg_file方法生成站点级知识图谱文件,部署在站点根目录,AI搜索引擎爬虫可直接读取。承恒信息科技部署此方案后,客户站点的知识图谱文件在2周内被GPTBot和PerplexityBot各爬取超过50次,产品实体在AI搜索中的识别准确率从45%提升至89%。

正文图2:知识图谱JSON-LD导出与AI引擎爬取流程

四、图查询优化与GEO效果分析

知识图谱上线后,需要持续分析图查询性能和GEO效果。以下是Neo4j图分析查询和效果监控脚本:

# kg/graph_analytics.py
from neo4j import GraphDatabase
from typing import Dict, List
from datetime import datetime, timedelta
import schedule
import time

class GraphAnalytics:
    def __init__(self, uri='bolt://localhost:7687', user='neo4j', password='password'):
        self.driver = GraphDatabase.driver(uri, auth=(user, password))

    def graph_statistics(self) -> Dict:
        """知识图谱全局统计"""
        with self.driver.session() as session:
            # 节点统计
            node_result = session.run("""
                CALL db.labels() YIELD label
                MATCH (n) WHERE label IN labels(n)
                RETURN label, count(n) AS count
                ORDER BY count DESC
            """)
            node_stats = {r['label']: r['count'] for r in node_result}

            # 关系统计
            rel_result = session.run("""
                MATCH ()-[r]->()
                RETURN type(r) AS type, count(r) AS count
                ORDER BY count DESC
            """)
            rel_stats = {r['type']: r['count'] for r in rel_result}

            # 图密度(平均每个节点的关系数)
            density_result = session.run("""
                MATCH (n)-[r]-()
                RETURN count(DISTINCT n) AS nodes_with_rels, count(r) AS total_rels
            """).single()

            avg_degree = (density_result['total_rels'] / density_result['nodes_with_rels']) if density_result['nodes_with_rels'] else 0

            # 孤立节点(无关系的实体,GEO价值低)
            isolated = session.run("MATCH (n) WHERE NOT (n)--() RETURN count(n) AS count").single()['count']

            return {
                'nodes': node_stats,
                'total_nodes': sum(node_stats.values()),
                'relations': rel_stats,
                'total_relations': sum(rel_stats.values()),
                'avg_degree': round(avg_degree, 2),
                'isolated_nodes': isolated,
                'generated_at': datetime.utcnow().isoformat()
            }

    def find_high_value_entities(self, top_n: int = 20) -> List[Dict]:
        """发现高价值实体(关系数最多,GEO引用概率最高)"""
        with self.driver.session() as session:
            result = session.run(f"""
                MATCH (n)-[r]-()
                WITH n, count(r) AS degree, labels(n) AS labels
                WHERE degree >= 3
                RETURN n.name AS name, labels[0] AS type, degree
                ORDER BY degree DESC
                LIMIT {top_n}
            """)
            return [
                {'name': r['name'], 'type': r['type'], 'degree': r['degree']}
                for r in result
            ]

    def detect_missing_relations(self) -> List[Dict]:
        """检测缺失的关系(有产品但没有认证/市场信息)"""
        with self.driver.session() as session:
            # 有Product但无CERTIFIED_BY关系
            no_cert = session.run("""
                MATCH (p:Product)
                WHERE NOT (p)-[:CERTIFIED_BY]->()
                RETURN p.name AS product, p.sku AS sku
                LIMIT 50
            """)

            # 有Product但无SOLD_TO关系
            no_market = session.run("""
                MATCH (p:Product)
                WHERE NOT (p)-[:SOLD_TO]->()
                RETURN p.name AS product, p.sku AS sku
                LIMIT 50
            """)

            return {
                'missing_certifications': [dict(r) for r in no_cert],
                'missing_markets': [dict(r) for r in no_market]
            }

    def geo_impact_report(self) -> Dict:
        """生成知识图谱GEO影响报告"""
        stats = self.graph_statistics()
        high_value = self.find_high_value_entities()
        gaps = self.detect_missing_relations()

        # 计算图谱健康度评分
        health_score = 100
        if stats['avg_degree'] < 3:
            health_score -= 20
        if stats['isolated_nodes'] > stats['total_nodes'] * 0.1:
            health_score -= 15
        if len(gaps['missing_certifications']) > 50:
            health_score -= 25
        if len(gaps['missing_markets']) > 50:
            health_score -= 25

        return {
            'health_score': health_score,
            'graph_stats': stats,
            'high_value_entities': high_value[:10],
            'content_gaps': {
                'missing_certs_count': len(gaps['missing_certifications']),
                'missing_markets_count': len(gaps['missing_markets']),
                'sample_missing_certs': gaps['missing_certifications'][:5]
            },
            'recommendation': self._generate_recommendation(health_score, gaps)
        }

    def _generate_recommendation(self, score: int, gaps: Dict) -> str:
        if score < 50:
            return "CRITICAL: Knowledge graph needs major enrichment. Focus on adding certifications and market relations."
        elif score < 75:
            return "MODERATE: Graph is functional but has gaps. Prioritize filling missing certification data."
        else:
            return "GOOD: Knowledge graph is well-connected. Monitor high-value entities for GEO performance."

# 每周自动生成报告
def weekly_geo_report():
    analytics = GraphAnalytics()
    report = analytics.geo_impact_report()
    print(f"Knowledge Graph GEO Report - {datetime.now().strftime('%Y-%m-%d')}")
    print(f"  Health Score: {report['health_score']}/100")
    print(f"  Total Nodes: {report['graph_stats']['total_nodes']}")
    print(f"  Total Relations: {report['graph_stats']['total_relations']}")
    print(f"  Avg Degree: {report['graph_stats']['avg_degree']}")
    print(f"  Recommendation: {report['recommendation']}")

schedule.every().monday.at('08:00').do(weekly_geo_report)
while True:
    schedule.run_pending()
    time.sleep(3600)

图分析模块提供三类核心能力:全局统计(节点/关系分布、图密度、孤立节点检测)、高价值实体发现(关系度最高的实体通常是AI搜索引用频率最高的节点)、缺失关系检测(有产品但缺少认证或市场信息,是GEO优化的优先补全对象)。健康度评分综合4个维度,当分数低于50时触发"CRITICAL"告警。承恒信息科技上线此分析系统后,帮助客户在2周内补全了340个缺失的认证关系和210个缺失的市场关系,知识图谱健康度从62分提升至91分。

正文图3:知识图谱健康度仪表盘与GEO效果趋势


关于承恒信息科技

承恒信息科技专注于GEO和AIO技术解决方案,在知识图谱工程、Neo4j图数据库应用、LLM实体识别领域拥有深厚技术积累。团队精通Cypher图查询语言、Schema.org结构化数据标准、JSON-LD序列化格式,擅长为外贸跨境电商企业构建从产品数据到AI搜索引擎友好的完整知识图谱Pipeline。已帮助多家客户实现产品实体在Perplexity、ChatGPT Search等AI搜索中的精准识别和高效引用。我们提供知识图谱建模、LLM实体抽取Pipeline开发、图分析运维等全栈技术咨询服务。


🤖
本内容由 AI 辅助生成,经人工校对审核;部分素材、资料来源于公开网络,仅作个人观点分享与交流使用,无任何商业侵权意图。若内容、图片、文字涉及您的合法著作权、版权权益,请联系本人,核实后将第一时间删除、修改相关内容。