HtmlParser 错误解决 character mismatch (new: [] != old: []) for encoding change from ...

错误:

character mismatch (new: 军 [0x519b] != old: [0xbe¾]) for encoding change from ISO-8859-1 to GB2312 at character offset 186。

原因:

如果请求url返回的页面上中文的title写在了meta的前面,而且这个meta里设置的charset编码信息又和parser默认的或者使用者自己设置定编码不一样。

那么就会报这个错,但是如果meta之前没有任何中文,那么就不会报这个错。因为htmlparser本身会根据html源码中的charset信息设置编码:<原创网址:http://hi.csdn.net/space-8079523.html,作者:蛰伏神兽>

让我们就根据报出的错误栈来看下htmlparser源码,来看看到底是怎么回事:

org.htmlparser.util.EncodingChangeException: character mismatch (new: 军 [0x519b] != old: [0xbe¾]) for encoding change from ISO-8859-1 to GB2312 at character offset 186 at org.htmlparser.lexer.InputStreamSource.setEncoding(InputStreamSource.java:279) at org.htmlparser.lexer.Page.setEncoding(Page.java:864) at org.htmlparser.tags.MetaTag.doSemanticAction(MetaTag.java:149) at org.htmlparser.scanners.TagScanner.scan(TagScanner.java:68) at org.htmlparser.scanners.CompositeTagScanner.scan(CompositeTagScanner.java:159) at org.htmlparser.util.IteratorImpl.nextNode(IteratorImpl.java:91) at org.htmlparser.Parser.parse(Parser.java:700)

先来看下parser解析Node的流程,

parser会用IteratorImpl的nextNode()对指定的html源码一行一行的往下解析然后一个一个产生node,没产生一个Node就会用这个Node的扫描器扫描这个Node来构建子节点,或者执行一些操作。当遇到meta标签的时候他就会产生一个MetaTag标签,并且用对应Scanner(TagScanner)扫描这个标签:

public Tag scan (Tag tag, Lexer lexer, NodeList stack) throws ParserException { tag.doSemanticAction (); return (tag); }这个方法中科院看到实际上这个方法就是执行了一下MetaTag的doSemanticAction();方法:

public void doSemanticAction () throws ParserException { String httpEquiv; String charset; httpEquiv = getHttpEquiv ();//原创网址:http://hi.csdn.net/space-8079523.html,作者:蛰伏神兽 if ("Content-Type".equalsIgnoreCase (httpEquiv)) { charset = getPage ().getCharset (getAttribute ("CONTENT")); getPage ().setEncoding (charset); } }很明显这个方法的作用就是获取刚刚产生的MetaTag节点中的Content信息,编码信息,然后设置当前页面的编码。

也就是说htmlparser其实有自动搜索页面源码的编码的能力,<原创网址:http://hi.csdn.net/space-8079523.html,作者:蛰伏神兽
>在遍历整个html源码信息,一个一个产生Node的时候就开始设置了。

但是这必须要执行到拥有编码信息的Meta行才会发现这个页面的编码。那么也就是说之前用来解析的默认编码集很有可能和这个html页面编码是不一致的。

我们再来看看出错源的那段代码:

public void setEncoding (String character_set) throws ParserException { String encoding; InputStream stream; char[] buffer; int offset; char[] new_chars; encoding = getEncoding (); if (!encoding.equalsIgnoreCase (character_set)) //绿色 { stream = getStream (); try { buffer = mBuffer; offset = mOffset; stream.reset (); try { mEncoding = character_set; mReader = new InputStreamReader (stream, character_set); mBuffer = new char[mBuffer.length]; mLevel = 0; mOffset = 0; mMark = -1; if (0 != offset) { new_chars = new char[offset]; if (offset != read (new_chars)) throw new ParserException ("reset stream failed"); for (int i = 0; i < offset; i++) if(new_chars[i]!=buffer[i]) throw new EncodingChangeException ("character mismatch (new: " + new_chars[i] + " [0x" + Integer.toString (new_chars[i], 16) + "] != old: " + " [0x" + Integer.toString (buffer[i], 16) + buffer[i] + "]) for encoding change from " + encoding + " to " + character_set + " at character offset " + i); } } catch (IOException ioe) { throw new ParserException (ioe.getMessage (), ioe); } } catch (IOException ioe) { // bug #1044707 mark()/reset() issues throw new ParserException ("Stream reset failed (" + ioe.getMessage () + "), try wrapping it with a org.htmlparser.lexer.Stream", ioe); } } }之前MetaTag的doSematicAction();中的setEcoding(charset)最终执行的代码就是这一部分。<原创网址:http://hi.csdn.net/space-8079523.html,作者:蛰伏神兽
>
也就是说如果默认的编码集和从后面meta信息中扫描的编码集不同的话,那么就会执行绿色的这一段代码。它会将Source,reset掉,然后用新的编码集来转换之前用默认编码集转换的那部分字符串。但是很明显,如果两次编码集不一样,转换出来的英文字符串一样,中文字符串却往往会不一样。也就是说,当我解析到meta信息的时候如果meta之前没有中文信息(有些网站会把title标签写在meta信息之前比如说铁血网首页www.tiexue.net)那么不管编码是否一致都不会报错,因为报错的那部分代码是的这一段

if(new_chars[i]!=buffer[i])先后两种编码集转换的字符串要不一致才会报错,如果meta信息之前全是英文那肯定一致,也就不会报错了。


说到这里应该会有人和我一样有个疑问了:htmlparser的作者们为什么要设置这个错误类型呢?<原创网址:http://hi.csdn.net/space-8079523.html,作者:蛰伏神兽
>两种不同编码集转换的源代码,如果是中文的话肯定会不一致!

感觉这个错误类型完全没什么用啊。所以我解决这个错误的方法就是修改这部分源码,把这一段给注释掉~(就让他不一样吧,肯定不一样的啦。因为编码不同啦,所以不要报错啦):修改后代码:

public void setEncoding (String character_set) throws ParserException { String encoding; InputStream stream; char[] buffer; int offset; char[] new_chars; encoding = getEncoding (); if (!encoding.equalsIgnoreCase (character_set)) { stream = getStream (); try { buffer = mBuffer; offset = mOffset; stream.reset (); try { mEncoding = character_set; mReader = new InputStreamReader (stream, character_set); mBuffer = new char[mBuffer.length]; mLevel = 0; mOffset = 0; mMark = -1; if (0 != offset) { new_chars = new char[offset]; if (offset != read (new_chars)) throw new ParserException ("reset stream failed"); /*for (int i = 0; i < offset; i++) if (new_chars[i] != buffer[i]) throw new EncodingChangeException ("character mismatch (new: " + new_chars[i] + " [0x" + Integer.toString (new_chars[i], 16) + "] != old: " + " [0x" + Integer.toString (buffer[i], 16) + buffer[i] + "]) for encoding change from " + encoding + " to " + character_set + " at character offset " + i);*/ } } catch (IOException ioe) { throw new ParserException (ioe.getMessage (), ioe); } } catch (IOException ioe) { // bug #1044707 mark()/reset() issues throw new ParserException ("Stream reset failed (" + ioe.getMessage () + "), try wrapping it with a org.htmlparser.lexer.Stream", ioe); }//原创网址:http://hi.csdn.net/space-8079523.html,作者:蛰伏神兽 } }好了.这样就行了,不管meta在title之前还是title之后都没关系了。htmlparser都会自动获取charset信息并且更改默认编码了。

大家有不同的看法欢迎留言讨论.转载请注明出处!谢谢啦。




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
import http.client from html.parser import HTMLParser import argparse from concurrent.futures import ThreadPoolExecutor import multiprocessing.pool prefix = "save/" readed_path = multiprocessing.Manager().list() cur_path = multiprocessing.Manager().list() new_path = multiprocessing.Manager().list() lock = multiprocessing.Lock() class MyHttpParser(HTMLParser): def __init__(self): HTMLParser.__init__(self) self.tag = [] self.href = "" self.txt = "" def handle_starttag(self, tag, attrs): self.tag.append(tag) # print("start tag in list :" + str(self.tag)) if tag == "a": for att in attrs: if att[0] == 'href': self.href = att[1] def handle_endtag(self, tag): if tag == "a" and len(self.tag) > 2 and self.tag[-2] == "div": print("in div, link txt is %s ." % self.txt) print("in div, link url is %s ." % self.href) lock.acquire() if not self.href in readed_path: readed_path.append(self.href) new_path.append(self.href) # print("end tag in list :" + str(self.tag)) lock.release() self.tag.pop(-1) def handle_data(self, data): if len(self.tag) >= 1 and self.tag[-1] == "a": self.txt = data def LoadHtml(path, file_path): if len(file_path) == 0: file_path = "/" conn = http.client.HTTPConnection(path) try: conn.request("GET", file_path) response = conn.getresponse() print(response.status, response.reason, response.version) data = response.read().decode("utf-8") if response.status == 301: data = response.getheader("Location") lock.acquire() new_path.append(data) lock.release() data = "" #print(data) conn.close() return data except Exception as e: print(e.args) def ParseArgs(): # 初始化解析器 parser = argparse.ArgumentParser() # 定义参数 parser.add_argument("-p", "--path", help="域名") parser.add_argument("-d", "--deep", type=int, help="递归深度") # 解析 args = parser.parse_args() return args def formatPath(path): path = path.removeprefix("https://") path = path.removeprefix("http://") path = path.removeprefix("//") return path def doWork(path): path = formatPath(path) m = path.find("/") if m == -1: m = len(path) data = LoadHtml(path[:m], path[m:]) with open(prefix + path[:m] + ".html", "w+", encoding="utf-8") as f: f.write(data) parse.feed(data) def work(deep,maxdeep): if deep > maxdeep: return args = ParseArgs() cur_path.append(formatPath(args.path)) readed_path.append(formatPath(args.path)) parse = MyHttpParser() e = multiprocessing.Pool(4) for i in range(args.deep): size = len(cur_path) e.map(doWork,cur_path) cur_path[:]=[] for p in new_path: cur_path.append(p) new_path[:]=[] print(i)优化此代码能在windows下运行
最新发布
06-03

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值