Rails中URI.open导入TXT文件时标点丢失问题求助
问题排查与解决方案
问题背景
在Rails项目中,通过summon_text方法从AWS S3读取后缀为.txt的HTML文件,执行Chapter.find_each(&:summon_text)后,文本能导入但特殊标点(如撇号’、双引号“”)丢失或错误(如hadn’t变成hadnt,“No,”变成No,),同时出现ASCII转译错误,且使用binary类型存储text字段。
问题原因分析
- 编码处理逻辑错误:代码中先对URL进行
encode('UTF-8','ISO-8859-1', ...)转码,再以r:UTF-8模式读取文件。但S3文件的实际编码可能并非ISO-8859-1,强制转码会导致特殊字符被替换为?或直接丢失;同时r:UTF-8模式如果遇到不符合UTF-8的字节,会自动丢弃或替换非ASCII字符。 - HTML实体未解码:目标文件实际是HTML格式,其中的特殊标点可能以HTML实体形式存在(如
’表示撇号’,“表示左双引号“),直接读取未解码的话,这些实体要么被当作普通文本,要么在转码过程中被错误处理。 - Binary字段存储不匹配:使用binary类型存储文本内容,binary类型按原始字节存储,没有编码元信息,Rails在读写时可能默认以ASCII编码处理,导致非ASCII的特殊字符被丢失或损坏。
解决方案
1. 修正文件读取的编码处理逻辑
去掉对URL的不必要转码,改用二进制模式读取文件原始字节,再根据实际编码进行转换(优先尝试UTF-8,失败则 fallback 到ISO-8859-1):
def summon_text require 'open-uri' return if text.present? url = "https://the-petulant-poetess.s3.us-east-2.amazonaws.com/stories/#{story.author.id}/#{id}.txt" puts url # 二进制读取原始字节,避免提前转码丢失信息 raw_data = URI.open(url, 'rb') { |f| f.read } # 尝试UTF-8解码,失败则用ISO-8859-1转码为UTF-8 readtext = begin raw_data.force_encoding('UTF-8').encode! rescue EncodingError raw_data.force_encoding('ISO-8859-1').encode('UTF-8') end if readtext.present? update(text: readtext) save! else puts "ISSUE WITH #{url}" end end
2. 解码HTML实体
因为文件实际是HTML格式,需要解码其中的HTML实体还原特殊标点。使用htmlentities gem(Rails默认可能已包含,若未添加则在Gemfile中加入gem 'htmlentities'并执行bundle install):
def summon_text require 'open-uri' require 'htmlentities' return if text.present? url = "https://the-petulant-poetess.s3.us-east-2.amazonaws.com/stories/#{story.author.id}/#{id}.txt" puts url raw_data = URI.open(url, 'rb') { |f| f.read } readtext = begin raw_data.force_encoding('UTF-8').encode! rescue EncodingError raw_data.force_encoding('ISO-8859-1').encode('UTF-8') end # 解码HTML实体,还原特殊标点 decoder = HTMLEntities.new decoded_text = decoder.decode(readtext) if decoded_text.present? update(text: decoded_text) save! else puts "ISSUE WITH #{url}" end end
3. 修正数据库字段类型
将存储文本的字段从binary改为text(或longtext,根据内容长度),text类型支持UTF-8编码,能正确保留特殊字符:
- 生成迁移文件:
rails generate migration ChangeTextTypeInChapters
- 编辑迁移文件:
class ChangeTextTypeInChapters < ActiveRecord::Migration[7.0] def change change_column :chapters, :text, :text # 确保字段使用UTF-8mb4编码,支持全量Unicode字符 execute "ALTER TABLE chapters MODIFY text TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" end end
- 执行迁移:
rails db:migrate
4. 验证与重新导入
清空已导入的错误文本,重新执行导入命令:
Chapter.update_all(text: nil) Chapter.find_each(&:summon_text)
内容的提问来源于stack exchange,提问作者Liz
相关产品推荐
相关产品推荐

