14

たくさんの HTML ファイルがあります。他のすべてのコンテンツを変更せずに、一部の要素を置き換えたい。たとえば、次の jQuery 式 (またはそれに相当するもの) を実行したいと思います。

$('.header .title').text('my new content')

次の HTML ドキュメントで:

<div class=header><span class=title>Foo</span></div>
<p>1<p>2
<table><tr><td>1</td></tr></table>

次の結果が得られます。

<div class=header><span class=title>my new content</span></div>
<p>1<p>2
<table><tr><td>1</td></tr></table>

問題は、私が試したすべてのパーサー ( NokogiriBeautifulSouphtml5lib ) が次のようなものにシリアル化することです。

<html>
  <head></head>
  <body>
    <div class=header><span class=title>my new content</span></div>
    <p>1</p><p>2</p>
    <table><tbody><tr><td>1</td></tr></tbody></table>
  </body>
</html>

たとえば、次のように追加します。

  1. html、head および body 要素
  2. p タグを閉じる
  3. ボディ

私のニーズを満たすパーサーはありますか? Node.js、Ruby、または Python のいずれかで動作するはずです。

4

7 に答える 7

11

Python 用のpyqueryパッケージを強くお勧めします。これは、 libxml2 への python バインディングである、非常に信頼性の高いlxmlパッケージの上に重ねられた jquery に似たインターフェイスです。

これは、非常に使い慣れたインターフェースで、まさにあなたが望むものだと思います。

from pyquery import PyQuery as pq
html = '''
<div class=header><span class=title>Foo</span></div>
<p>1<p>2
<table><tr><td>1</td></tr></table>
'''
doc = pq(html)

doc('.header .title').text('my new content')
print doc

出力:

<div><div class="header"><span class="title">my new content</span></div>
<p>1</p><p>2
</p><table><tr><td>1</td></tr></table></div>

終了の p タグは仕方ありません。元のドキュメントの気まぐれではなく、元のドキュメントのlxmlのみを保持します。段落は 2 つの方法で作成でき、シリアライズを行う場合はより標準的な方法を選択します。より優れた (バグのない) パーサーが見つかるとは思えません。

于 2012-07-20T16:20:30.790 に答える
6

注:私はPython 3を使用しています。

これは CSS セレクターのサブセットのみを処理しますが、目的には十分かもしれません。

from html.parser import HTMLParser

class AttrQuery():
    def __init__(self):
        self.repl_text = ""
        self.selectors = []

    def add_css_sel(self, seltext):
        sels = seltext.split(" ")

        for selector in sels:
            if selector[:1] == "#":
                self.add_selector({"id": selector[1:]})
            elif selector[:1] == ".":
                self.add_selector({"class": selector[1:]})
            elif "." in selector:
                html_tag, html_class = selector.split(".")
                self.add_selector({"html_tag": html_tag, "class": html_class})
            else:
                self.add_selector({"html_tag": selector})

    def add_selector(self, selector_dict):
        self.selectors.append(selector_dict)

    def match_test(self, tagwithattrs_list):
        for selector in self.selectors:
            for condition in selector:
                condition_value = selector[condition]
                if not self._condition_test(tagwithattrs_list, condition, condition_value):
                    return False
        return True

    def _condition_test(self, tagwithattrs_list, condition, condition_value):
        for tagwithattrs in tagwithattrs_list:
            try:
                if condition_value == tagwithattrs[condition]:
                    return True
            except KeyError:
                pass
        return False


class HTMLAttrParser(HTMLParser):
    def __init__(self, html, **kwargs):
        super().__init__(self, **kwargs)
        self.tagwithattrs_list = []
        self.queries = []
        self.matchrepl_list = []
        self.html = html

    def handle_starttag(self, tag, attrs):
        tagwithattrs = dict(attrs)
        tagwithattrs["html_tag"] = tag
        self.tagwithattrs_list.append(tagwithattrs)

        if debug:
            print("push\t", end="")
            for attrname in tagwithattrs:
                print("{}:{}, ".format(attrname, tagwithattrs[attrname]), end="")
            print("")

    def handle_endtag(self, tag):
        try:
            while True:
                tagwithattrs = self.tagwithattrs_list.pop()
                if debug:
                    print("pop \t", end="")
                    for attrname in tagwithattrs:
                        print("{}:{}, ".format(attrname, tagwithattrs[attrname]), end="")
                    print("")
                if tag == tagwithattrs["html_tag"]: break
        except IndexError:
            raise IndexError("Found a close-tag for a non-existent element.")

    def handle_data(self, data):
        if self.tagwithattrs_list:
            for query in self.queries:
                if query.match_test(self.tagwithattrs_list):
                    line, position = self.getpos()
                    length = len(data)
                    match_replace = (line-1, position, length, query.repl_text)
                    self.matchrepl_list.append(match_replace)

    def addquery(self, query):
        self.queries.append(query)

    def transform(self):
        split_html = self.html.split("\n")
        self.matchrepl_list.reverse()
        if debug: print ("\nreversed list of matches (line, position, len, repl_text):\n{}\n".format(self.matchrepl_list))

        for line, position, length, repl_text in self.matchrepl_list:
            oldline = split_html[line]
            newline = oldline[:position] + repl_text + oldline[position+length:]
            split_html = split_html[:line] + [newline] + split_html[line+1:]

        return "\n".join(split_html)

以下の使用例を参照してください。

html_test = """<div class=header><span class=title>Foo</span></div>
<p>1<p>2
<table><tr><td class=hi><div id=there>1</div></td></tr></table>"""

debug = False
parser = HTMLAttrParser(html_test)

query = AttrQuery()
query.repl_text = "Bar"
query.add_selector({"html_tag": "div", "class": "header"})
query.add_selector({"class": "title"})
parser.addquery(query)

query = AttrQuery()
query.repl_text = "InTable"
query.add_css_sel("table tr td.hi #there")
parser.addquery(query)

parser.feed(html_test)

transformed_html = parser.transform()
print("transformed html:\n{}".format(transformed_html))

出力:

transformed html:
<div class=header><span class=title>Bar</span></div>
<p>1<p>2
<table><tr><td class=hi><div id=there>InTable</div></td></tr></table>
于 2012-08-11T18:14:29.120 に答える
4

いくつかの言語でこれを行ったことがありますが、空白や HTML コメントさえも保持する、私が見た中で最高のパーサーは次のとおりです。

残念ながらJavaのJerichoです。

つまり、Jericho はフラグメントを解析して保存する方法を知っています。

はい、私はその Java を知っていますが、ペイロードを取得して変換する Java を少しだけ使用して、RESTful サービスを簡単に作成できます。Java REST サービスでは、JRuby、Jython、Rhino Javascript などを使用して Jericho と連携できます。

于 2012-08-16T19:46:06.880 に答える
2

これには、NokogiriHTMLフラグメントを使用できます。

fragment = Nokogiri::HTML.fragment('<div class=header><span class=title>Foo</span></div>
                                    <p>1<p>2
                                    <table><tr><td>1</td></tr></table>')

fragment.css('.title').children.first.replace(Nokogiri::XML::Text.new('HEY', fragment))

frament.to_s #=> "<div class=\"header\"><span class=\"title\">HEY</span></div>\n<p>1</p><p>2\n</p><table><tr><td>1</td></tr></table>" 

タグは無効なHTMLであるため、問題は解決しpませんが、これにより、html、headまたはbody、およびtbodyタグのないドキュメントが返されます。

于 2012-07-20T16:01:27.263 に答える
1

Pythonの場合 - 使用はlxml.htmlかなり簡単です: (ポイント 1 と 3 を満たしていますが、2 についてはあまりできないと思います。引用符で囲まれていない を処理しますclass=)

import lxml.html

fragment = """<div class=header><span class=title>Foo</span></div>
<p>1<p>2
<table><tr><td>1</td></tr></table>
"""

page = lxml.html.fromstring(fragment)
for span in page.cssselect('.header .title'):
    span.text = 'my new value'
print lxml.html.tostring(page, pretty_print=True)

結果:

<div>
<div class="header"><span class="title">my new content</span></div>
<p>1</p>
<p>2
</p>
<table><tr><td>1</td></tr></table>
</div>
于 2012-07-20T16:33:53.620 に答える
0

Node.js アプリを実行している場合、このモジュールは、JQuery スタイルの DOM マニピュレーターです: https://github.com/cheeriojs/cheerio

彼らのウィキからの例:

var cheerio = require('cheerio'),
$ = cheerio.load('<h2 class="title">Hello world</h2>');

$('h2.title').text('Hello there!');
$('h2').addClass('welcome');

$.html();
//=> <h2 class="title welcome">Hello there!</h2>
于 2015-05-12T15:48:26.640 に答える
0

これは少し別の解決策ですが、これがいくつかの単純な例にすぎない場合は、おそらく CSS が答えです。

生成されたコンテンツ

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
  <head>
    <style type="text/css">
    #header.title1:first-child:before {
      content: "This is your title!";
      display: block;
      width: 100%;
    }
    #header.title2:first-child:before {
      content: "This is your other title!";
      display: block;
      width: 100%;
    }
    </style>

  </head>
  <body>
   <div id="header" class="title1">
    <span class="non-title">Blah Blah Blah Blah</span>
   </div>
  </body>
</html>

この例では、jQuery でクラスを交換するだけで、css を使用して変更を無料で取得できます。この特定の使用法はテストしていませんが、動作するはずです。

これは停止メッセージなどに使用します。

于 2012-08-15T16:50:40.640 に答える