coogle / recipe-parser
Requires
- php: >=5.6
- ext-curl: *
This package is not auto-updated.
Last update: 2024-09-23 14:15:56 UTC
README
这是一个用于从HTML中解析菜谱数据的PHP库。它是另一个项目的分支,除了使用PHP命名空间(以及其他一些升级)外。
用法
将此库放置在您可以从PHP代码中加载的地方。调用RecipeParser::parse()
,传入表示包含菜谱的HTML文件内容的DomDocument对象,以及可选的原始页面URL,这有助于识别要使用的特定解析器。返回值是一个包含菜谱数据的PHP对象。
$doc = RecipeParser_Text::getDomDocument($html);
$recipe = RecipeParser::parse($doc, $url);
print_r($recipe);
输出
RecipeStruct Object
(
[title] => Chai-Spiced Hot Chocolate
[description] =>
[notes] =>
[yield] => 6 servings
[source] => Bon Appétit
[url] => http://www.bonappetit.com/recipes/quick-recipes/2010/02/chai_spiced_hot_chocolate
[categories] => Array
(
)
[photo_url] => http://www.bonappetit.com/wp-content/uploads/2011/01/mare_chai_spiced_hot_chocolate_h1.jpg
[status] => recipe
[time] => Array
(
[prep] => 15
[cook] => 0
[total] => 25
)
[ingredients] => Array
(
[0] => Array
(
[name] =>
[list] => Array
(
[0] => 4 cups low-fat (1%) milk
[1] => 3/4 cup bittersweet chocolate chips
[2] => 10 cardamom pods, coarsely cracked
[3] => 1/2 teaspoon whole allspice, cracked
[4] => 2 cinnamon sticks, broken in half
[5] => 1/2 teaspoon freshly ground black pepper
[6] => 5 tablespoons (packed) golden brown sugar, divided
[7] => 6 quarter-size slices fresh ginger plus 1/2 teaspoon grated peeled fresh ginger
[8] => 1 teaspoon vanilla extract, divided
[9] => 1/2 cup chilled whipping cream
)
)
)
[instructions] => Array
(
[0] => Array
(
[name] =>
[list] => Array
(
[0] => Combine first 6 ingredients, 4 tablespoons brown sugar, and ginger slices in medium saucepan. Bring almost to simmer, whisking frequently. Remove from heat; cover and steep 10 minutes. Mix in 1/2 teaspoon vanilla.
[1] => Meanwhile, whisk cream, remaining 1 tablespoon brown sugar, grated ginger, and remaining 1/2 teaspoon vanilla in medium bowl to peaks.
[2] => Strain hot chocolate. Ladle into 6 mugs. Top each with dollop of ginger cream.
)
)
)
[credits] =>
)
此外,还有一个命令行脚本可以演示库的用法
$ ./bin/parse_recipe tests/data/bonappetit_com_special_sunday_roast_chicken_curl.html
或
$ ./bin/parse_recipe http://www.cooks.com/recipe/3k38r484/baked-ziti.html
可以使用命令行脚本作为获取菜谱JSON表示的一种简单方式
$ ./bin/parse_recipe http://www.cooks.com/recipe/3k38r484/baked-ziti.html json
{"title":"Baked Ziti","description":"","credits":"","notes":"","yield":"","source":1,"url":"http:\/\/www.cooks.com\/recipe\/3k38r484\/baked-ziti.html","categories":[],"photo_url":"","time":{"prep":0,"cook":0,"total":0},"ingredients":[{"name":"","list":["4 cups ziti pasta, uncooked","1 26 oz. jar or can pasta sauce","3 cloves garlic, minced","1\/2 tsp. dried or 1 tbsp. fresh basil, chopped","1 can (14-1\/2 oz.) diced tomatoes, with juice","1 4 oz. pkg cream cheese","1 cup ricotta cheese","2 eggs","1\/4 cup fresh parsley, chopped","1\/2 lb. (1 small pkg) shredded Mozzarella Cheese","1\/3 cup Parmesan cheese, grated"]}],"instructions":[{"name":"","list":["Preheat oven to 375\u00baF.","In a large saucepan boil pasta according to package directions. Drain in colander.","In same pan, combine pasta sauce, garlic, basil, diced tomatoes and chunks of cream cheese. Stir and cook over medium heat until cream cheese melts and mixture is well combine (about 5 minutes). Stir in pasta and mix well.","Spread half of the pasta mixture in a 13x9-inch baking dish.","Combine eggs, ricotta and parsley. Spread over mixture in baking dish in a layer, top with 1 cup mozzarella and another layer of ricotta. Top with remaining pasta mixture, mozzarella and Parmesan.","Bake, uncovered, in preheated oven for about 25-30 minutes. (If edges begin to brown, cover with foil)."]}]}
简介
菜谱解析器
该库的大部分由用于从大量非结构化HTML页面中提取或抓取结构化菜谱数据的类组成。从用户的角度来看,您最感兴趣的将是RecipeParser::parse()
方法。从开发者(贡献者)的角度来看,您将在lib/RecipeParser/Parsers/
类文件中找到解析例程。
解析例程主要由HTML DOM节点中的XPath查询组成。互联网上发现的许多菜谱都来自大型出版商网站上的大型目录,这些目录使用相对不经常更改的HTML模板渲染。通常,我们从HTML解析到结构化菜谱对象的每个字符串都(1)使用XPath查询在DOM中定位,然后(2)使用各种正则表达式和字符串替换进行清理(见RecipeParser_Text
类)。
知道网站HTML模板的格式在几个月或几年内不太可能发生重大变化,编写针对特定网站的解析器可以获得不错的回报。维护这些解析器需要大量的维护工作。hRecipe微格式、microdata(data-vocabulary.org、schema.org、RDFa)的日益普及使解析许多网站上的菜谱任务变得相对容易,尽管还不完美。谷歌对丰富片段的使用一直是许多网络出版商采用这些(相对)结构化格式的一个激励因素。
库中提供了一些“通用”解析器,这些解析器可以是自动由RecipeParser::parse()
根据HTML内容中的信号调用的,或者作为自定义解析器的起点使用。例如,在RecipeParser/Parser/Bonappetitcom.php
中的parse()
方法的前几行可以看到这种示例。
- MicrodataDataVocabulary.php
- MicrodataRdfDataVocabulary.php
- MicrodataSchema.php
- Microformat.php
使用哪个解析器?
解析算法必须确定将使用哪个解析器来处理任何给定的菜谱文件。通过将URL模式与菜谱的URL进行模式匹配来选择自定义解析器。每个自定义解析器都已在lib/RecipeParser/Parsers/parsers.ini
中注册,并具有相应的URL模式。当没有匹配到自定义模式时,RecipeParser
类将搜索HTML文件中的字符串,以指示使用微格式或microdata来标记菜谱。
许多自定义解析器将严重依赖于通用解析器作为收集菜谱数据的基础,然后使用额外的XPath查询和解析来覆盖或填补空白。
class RecipeParser_Parser_Bhgcom {
public function parse(DomDocument $doc, $url) {
$recipe = RecipeParser_Parser_MicrodataSchema::parse($doc, $url);
...snip...
// Notes -- Collect any tips/notes that appear at the end of the recipe instructions.
$notes = array();
$nodes = $xpath->query('//*[@class="recipeTips"]//li');
foreach ($nodes as $node) {
$value = RecipeParser_Text::FormatAsOneLine($node->nodeValue);
$value = preg_replace("/^(Tip|Note)\s*(.*)$/", "$2", $value);
$notes[] = $value;
}
为RecipeParser做出贡献
最大的两个需求是 编写新的食谱解析器 和 修复损坏的解析器(食谱网站的HTML结构经常变化,使得我们的解析查询过时)。
依赖项
- PHPUnit
编写新的食谱解析器
许多使用微格式或微数据的网站的食谱可以使用RecipeParser附带的一般解析器进行解析。但是,对于大多数网站,仍然需要编写自定义解析器,不幸的是,随着时间的推移,网站所有者对其HTML模板的更改导致这些解析器往往会出现损坏。
您可以为任何您感兴趣的网站编写新的解析器,或者在问题列表中找到请求的解析器。
1. 收集食谱的样本HTML文件
为了为特定网站制作最健壮的解析器,您应该找到一些来自该网站的食谱,这些食谱在元数据和格式上有所不同。例如,一些包含时间和产量,而另一些则不包含。一些网站在成分和说明列表中包含部分分隔符,这是一个很好的测试点。巧克力蛋糕配糖霜的食谱通常可以很好地进行测试。
bin
目录中的fetch_parser_test_file
脚本将下载一个食谱并将其保存到本地。该脚本还将将食谱源的一些元数据(包括URL)存储在文件的顶部HTML注释中。
$ ./fetch_parser_test_file http://www.elanaspantry.com/cranberry-coconut-power-bars/
Writing data file to /path/to/RecipeParser/tests/data
-rw-r--r-- 1 mbrittain staff 100412 Sep 8 21:49 elanaspantry_com_cranberry_coconut_power_bars_paleo_power_bars_curl.html
2. 生成单元测试的样板
运行import_test_boilerplate
脚本将为新的食谱文件生成一组单元测试的样板。样板代码将被输出到stdout,因此您应该将内容重定向到新的测试文件,如下所示
$ ./bin/import_test_boilerplate tests/data/elanaspantry_com_* > tests/RecipeParser_Parser_ElanaspantrycomTest.php
更新单元测试文件中PHPUnit测试类的名称。查找字符串"INSERTCLASSNAME"并替换它。
3. 为每个食谱编写测试断言
样板包含大多数我们关心的字段的空测试断言。标题、成分和说明是我们真正需要的字段,以便拥有可用的解析器。并非所有字段都会出现在食谱中,您可以自由删除无法从食谱的HTML中填充的字段的断言。但我们的目标是尽可能多地提取每个食谱的内容。
4. 编写RecipeParser_Parser_*类。
这里很难说更多,除了阅读一些现有的解析器并尝试以它们为例。
5. 添加到已注册解析器的列表中
diff --git a/lib/RecipeParser/Parser/parsers.ini b/lib/RecipeParser/Parser/parsers.ini
+[Elana's Pantry]
+pattern = "elanaspantry.com"
+parser = "Elanaspantrycom"
+
6. 验证所有测试都通过
是的,运行所有测试。
7. 总结
提交您的更改,并向onetsp/RecipeParser
提交拉取请求以合并您的更改。
修复损坏的解析器
如果您发现某个特定的食谱解析器不再工作,首先应该在该项目中打开一个问题,以便我们可以跟踪这些问题。您可能也在修复已被列入公开问题中的解析器。
1. 更新测试食谱文件
bin
目录中的fetch_all_test_files
脚本将下载每个测试文件的新副本,这些文件列在相应的单元测试文件中。此过程还将处理在运行解析程序之前应进行的某些字符编码和转义,因此请避免手动下载这些文件。
$ cd bin
./fetch_all_test_files ../tests/RecipeParser_Parser_BonappetitcomTest.php
Writing data file to /path/to/RecipeParser/tests/data
-rw-r--r-- 1 mbrittain staff 60734 Aug 31 22:22 bonappetit_com_beet_and_fennel_soup_with_kefir_curl.html
Writing data file to /path/to/RecipeParser/tests/data
-rw-r--r-- 1 mbrittain staff 60011 Aug 31 22:22 bonappetit_com_chai_spiced_hot_chocolate_curl.html
确认下载的文件存储在与原始测试文件相同的文件名下。文件名是从HTML文件中找到的<title>
派生的。如果标题的格式已更改,则新测试文件可能不匹配。
确定这一点最简单的方法是使用git status
查看测试文件是否列为准修改或未跟踪。如果它们未跟踪,您需要(1)更改单元测试中引用的文件名,以及(2)使用git rm
删除旧测试文件。
diff --git a/tests/RecipeParser_Parser_BonappetitcomTest.php b/tests/RecipeParser_Parser_BonappetitcomTest.php
index 5a247b8..b0a8a58 100644
public function test_chia_hot_chocolate() {
- $path = "data/bonappetit_com_chai_spiced_hot_chocolate_bon_app_tit_curl.html";
+ $path = "data/bonappetit_com_chai_spiced_hot_chocolate_curl.html";
$url = "http://www.bonappetit.com/recipes/quick-recipes/2010/02/chai_spiced_hot_chocolate";
2. 确定解析器失败的测试
运行解析器的单元测试,以找出哪些断言正在失败。
$ phpunit RecipeParser_Parser_BonappetitcomTest.php
您可以通过在命令行中设置 VERBOSE=1
来查看更多单元测试中的信息。这可能是诊断解析器主要问题的最简单方法,例如,如果整个指令列表已经丢失。
$ VERBOSE=1 phpunit RecipeParser_Parser_BonappetitcomTest.php
3. 修复解析器代码
解析器文件位于 /lib/RecipeParser/Parser
。
大多数解析器开始失败是因为网站所有者(例如FoodNetwork.com)已经更改了他们的HTML模板结构。因此,大多数修复将仅限于更新xpath查询以指向HTML文档中新的节点选择。
如果出版商已修改他们的HTML模板以使用微格式或微数据布局,您可能需要更新解析器以使用新的(或不同的)通用解析器。在这种情况下,尽可能多地依赖通用解析器的结果——只要这不需要在通用解析器中编写针对任何单个出版商标记过于具体的代码。
没有人正确实现微数据或微格式,所以我承认通用解析器和特定站点解析器之间的差异有点主观。
4. 确认所有单元测试
在修复解析器的同时,您将测试特定的解析器。在提交任何对解析器的更改之前,请确保也验证所有解析器测试都通过,因为一些解析器依赖于某些共享代码。
5. 总结
提交您的更改,并向onetsp/RecipeParser
提交拉取请求以合并您的更改。
有问题?
这份文档可能不完整。您可以通过 mike@onetsp.com(但请耐心等待,如果我的回复较慢)