返回

Azure 认知搜索 DocumentExtractionSkill 无法提取文本?

Ai

Azure 认知搜索中 DocumentExtractionSkill 无法从 Blob 数据提取文本的问题解决

在使用 Azure 认知搜索时, 遇到 DocumentExtractionSkill 不能从数据库的 VARBINARY(MAX) 类型字段中正确提取 PDF 等文件内容的现象,挺常见的。就是明明配置好了索引、技能集和索引器,但索引结果里的 contentmetadata 字段却是空的。 别急, 下面咱们一起分析原因并给出解决办法。

一、 问题原因分析

问题出在认知搜索服务如何访问和处理数据源上。当前配置, 索引器直接尝试从SQL 数据库读取 FileData (VARBINARY(MAX) 数据),但 DocumentExtractionSkill 的设计初衷是从Azure Blob 存储提取内容,而不是直接从SQL SERVER BLOB列。认知搜索不能直接“理解”数据库中存储的原始二进制数据。

我们需要一个桥梁,让认知搜索能识别和处理数据库中的二进制文件数据。

二、解决方案

要解决这个问题,关键在于把SQL数据库中的二进制数据,变成认知搜索能处理的格式。有以下几种方法:

1. 利用 Azure Blob 存储作为中间层

这是推荐的方法,也是最直接的方法。 认知搜索本来就擅长处理Blob存储的数据。

  • 原理: 将 SQL Server 中的文件数据导出到 Azure Blob 存储, 然后配置认知搜索从 Blob 存储提取内容。

  • 步骤:

    1. 创建 Azure Blob 存储账户和容器。 这个不用多说,在Azure门户里点几下鼠标就搞定。

    2. 将数据导出到 Blob 存储。 可以写一个简单的程序(例如 C# 或 Python)或者用Azure Data Factory从 SQL Server 读取 FileData,然后将每个文件上传到 Blob 存储的容器中。确保文件名唯一,可以用数据库里的IDFileName

      // C# 代码示例 (需要安装 Azure.Storage.Blobs NuGet 包)
      using Azure.Storage.Blobs;
      using System.Data.SqlClient;
      
      // ... 省略其他代码
      
      string connectionString = "你的 SQL Server 连接字符串";
      string blobConnectionString = "你的 Blob 存储连接字符串";
      string containerName = "你的 Blob 容器名";
      
      BlobServiceClient blobServiceClient = new BlobServiceClient(blobConnectionString);
      BlobContainerClient containerClient = blobServiceClient.GetBlobContainerClient(containerName);
      
      using (SqlConnection connection = new SqlConnection(connectionString))
      {
          connection.Open();
          string query = "SELECT ID, FileName, FileData FROM FileStorage";
          using (SqlCommand command = new SqlCommand(query, connection))
          {
              using (SqlDataReader reader = command.ExecuteReader())
              {
                  while (reader.Read())
                  {
                      int id = reader.GetInt32(0);
                      string fileName = reader.GetString(1);
                      byte[] fileData = (byte[])reader.GetValue(2);
      
                      // 使用唯一的文件名 (例如,ID + 文件名)
                      string blobName = 
      // C# 代码示例 (需要安装 Azure.Storage.Blobs NuGet 包)
      using Azure.Storage.Blobs;
      using System.Data.SqlClient;
      
      // ... 省略其他代码
      
      string connectionString = "你的 SQL Server 连接字符串";
      string blobConnectionString = "你的 Blob 存储连接字符串";
      string containerName = "你的 Blob 容器名";
      
      BlobServiceClient blobServiceClient = new BlobServiceClient(blobConnectionString);
      BlobContainerClient containerClient = blobServiceClient.GetBlobContainerClient(containerName);
      
      using (SqlConnection connection = new SqlConnection(connectionString))
      {
          connection.Open();
          string query = "SELECT ID, FileName, FileData FROM FileStorage";
          using (SqlCommand command = new SqlCommand(query, connection))
          {
              using (SqlDataReader reader = command.ExecuteReader())
              {
                  while (reader.Read())
                  {
                      int id = reader.GetInt32(0);
                      string fileName = reader.GetString(1);
                      byte[] fileData = (byte[])reader.GetValue(2);
      
                      // 使用唯一的文件名 (例如,ID + 文件名)
                      string blobName = $"{id}_{fileName}";
                      BlobClient blobClient = containerClient.GetBlobClient(blobName);
      
                       //分块上传
                      using (var stream = new MemoryStream(fileData))
                          blobClient.Upload(stream,overwrite:true);
      
                      //直接上传
                      //blobClient.Upload(new BinaryData(fileData));
      
                  }
              }
          }
      }
      
      quot;{id}_{fileName}"
      ; BlobClient blobClient = containerClient.GetBlobClient(blobName); //分块上传 using (var stream = new MemoryStream(fileData)) blobClient.Upload(stream,overwrite:true); //直接上传 //blobClient.Upload(new BinaryData(fileData)); } } } }
    3. 修改认知搜索配置。

      • 数据源 (Data Source): 指向 Azure Blob 存储容器。
      • 在“数据源”配置中,指定 “类型” 为 “Azure Blob 存储”,提供连接字符串。
      • 在"解析模式"那里,选择 "DetectAllText (为PDF和其他结构化或半结构化内容建议选择的项)".
      • 技能集 (Skillset): 保持 DocumentExtractionSkill 的配置不变,或者微调 parsingModedataToExtract 参数。
      • 索引器 (Indexer): 修改 fieldMappings,从 Blob 存储中的字段映射到索引中的字段。例如,"sourceFieldName": "/document/content" 对应 Blob 中的文件内容。
      • 关键配置, 将sourceFieldName指向blob:
       ```json
       "fieldMappings": [
          {
              "sourceFieldName": "metadata_storage_name",
              "targetFieldName": "fileName"
          },
          {
             "sourceFieldName": "metadata_storage_path",
              "targetFieldName": "id",
              "mappingFunction":{
                  "name":"base64Encode"
              }
          }
       ],
        "outputFieldMappings": [
           {
               "sourceFieldName": "/document/content",
               "targetFieldName": "content"
           },
            {
                  "sourceFieldName": "/document/metadata",
                 "targetFieldName": "metadata"
            }
        ]
      
       ```
      
      • 将ID 字段使用base64编码。
      • 这里面的metadata_storage_name对应数据库表里的fileName,认知搜索能自己提取到.
    4. 运行索引器。 重新运行索引器,它将从 Blob 存储读取文件,提取文本并填充到索引的 content 字段。

  • 安全建议:

    • 使用托管标识 (Managed Identities) 进行身份验证,避免在代码或配置中存储密钥。
    • 对 Blob 存储进行加密(静态加密和服务端加密)。
    • 使用网络安全组 (NSGs) 或防火墙限制对 Blob 存储的访问。

2. 自定义技能 (Custom Skill)

如果你不想使用 Azure Blob 存储, 或者有特殊处理需求,可以考虑创建自定义技能。

  • 原理: 自定义技能本质上是一个 Web API,它接收来自认知搜索的数据,进行处理,然后返回处理后的结果。 你可以在自定义技能中实现从 SQL Server 读取二进制数据、提取文本的逻辑。

  • 步骤:

    1. 创建 Web API。 可以使用 Azure Functions 或任何其他 Web API 框架(例如 ASP.NET Core)。这个 API 需要接收包含 FileData 的请求,提取文本(可以使用 iTextSharp 或 PdfSharp 等库处理 PDF),然后返回包含 contentmetadata 的响应。
    2. 部署 Web API。 将 Web API 部署到 Azure 或任何其他可公开访问的环境。
    3. 配置认知搜索。
      • 技能集 (Skillset): 添加一个自定义技能,指定 Web API 的 URL 和认证信息(如果需要)。inputs 部分应该包含 file_dataoutputs 部分包含 contentmetadata

          {
            "@odata.type": "#Microsoft.Skills.Custom.WebApiSkill",
            "name": "customSkill",
            "description": "Custom skill to extract text from SQL Server data",
            "context": "/document",
            "uri": "你的 Web API 的 URL",
            "httpHeaders": {}, // 如果需要,添加请求头
             "httpMethod": "POST",
            "timeout": "PT30S",
            "batchSize": 1,
            "inputs": [
             {
                 "name": "file_data",
                  "source": "/document/file_data"
              }
           ],
          "outputs": [
              {
               "name": "content",
                "targetName": "content"
              },
            {
               "name": "metadata",
               "targetName": "metadata"
              }
          ]
        }
        
      • 索引器 (Indexer): fieldMappingsoutputFieldMappings 与原来类似,但要确保 outputFieldMappings 中的 sourceFieldName 指向自定义技能的输出 (/document/content/document/metadata)。

    4. 运行索引器。 运行索引器,认知搜索会调用你的自定义技能来提取数据。
  • 进阶: 如果数据量非常大, 可以在自定义技能中使用异步处理和批处理, 提高效率。

  • 安全建议:

    • 使用HTTPS。
    • 使用API密钥或其他身份验证机制保护 Web API。

3.(不建议,有局限)直接在认知搜索数据源中配置转换

在某些情况下,如果你只处理文本类的数据库内容,并且不需要保留文件原本的二进制格式,可以直接在认知搜索数据源中配置转换, 比如直接将varbinary(max) cast为varchar(max), 让认知搜索尝试把二进制当成字符串。

  • 只适合处理简单文本情况,对于 PDF 这类非纯文本, 无效.

    -- 修改数据源的查询,将 FileData 转换为 VARCHAR(MAX)
    -- 适用于纯文本内容,或者你知道内容是UTF8,Latin1之类的编码。
    --   SELECT ID, FileName, CAST(FileData AS VARCHAR(MAX)) AS FileContent, UploadDate FROM FileStorage
    
    -- 对应的FiledMapping 要做更改
      "fieldMappings": [
        {
          "sourceFieldName": "fileContent",
          "targetFieldName": "content"
        }
        ]
    
    • 需要修改对应索引器,和字段对应.
  • 只能针对纯文本格式的文件有效,如果是PDF之类的,会得到一堆乱码.

三、总结和选择

  • 首推Azure Blob 存储 作为中间媒介的方法,符合认知搜索的常见用法,配置和操作简单,性能也好。
  • 如果有很特殊的要求,例如数据不能放到Blob,必须实时从数据库读取和处理,需要做复杂的数据转换, 可以选择自定义技能,但开发和维护成本会高一些。
  • 第三种,数据源直接转换,仅仅能处理纯文本类型文件,实际情况局限太大,不建议。

记住,无论选择哪种方法,关键都是为认知搜索的 DocumentExtractionSkill 提供它可以处理的输入数据。 通过以上这些步骤和分析,应该能解决你遇到的问题,让认知搜索正确提取到文件内容。