【问题标题】:How to parse HTML to search for content specific needles如何解析 HTML 以搜索特定内容的针
【发布时间】:2014-03-18 01:57:24
【问题描述】:

鉴于下面的NSString最初是从使用NSXMLParser解析XML文档中检索到的CData对象转换而来的,我如何获得这本书的以下属性:标题、书籍图片封面、作者、价格, 和评分?

这是我获取以下属性的基本解决方案

  1. 书名 - 我可能可以通过查看riRssTitle span 类来获得这个,但是我必须弄清楚如何读取 ahref url 标签之间的标题获取标题

  2. 图书图片 - 我必须获取第一个 URL http://ecx.images-amazon.com/images/I/41Lg22K3ViL._SL160_PIsitb-sticker-arrow-dp,TopRight,12,-18_SH30_OU02_.jpg,然后将所有内容保留到 http://ecx.images-amazon.com/images/I/41Lg22K3ViL,省略其余部分,然后附加 .jpg 标签以后有一个完整的图片检索网址。

  3. 图书作者 - 我必须按照与第 1 步相同的步骤,但要搜索riRssContributor span 标签。

  4. 图书价格 - 这里没有所有商品通用的价格标签,但我确实看到的一个共同点是价格总是在 font tag 中坐在BOLD tag

  5. Rating - 大概可以通过查找包含单词stars的URL然后抓取它后面的数字来检索,4表示4星,任何数字加上-5 意味着额外的 0.5 星。所以3-5 意味着 3.5 颗星。

在不弄乱的情况下,最好的方法是什么?此外,如果亚马逊决定更改其显示 URL 的方式,我也不喜欢我的代码会如何中断,我的应用程序依赖于亚马逊维护其 url 命名约定。

目前,这是最好的前进方式吗?有没有可以实现我想做的快速解析器?

这是亚马逊 RSS 提要的示例:http://www.amazon.co.uk/gp/rss/bestsellers/books/72/ref=zg_bs_72_rsslink


这是我为每个项目检索到的以下 CData NSString 数据。

<div style="float:left;">
    <a class="url" href="http://www.amazon.co.uk/Gone-Girl-Gillian-Flynn/dp/0753827662/ref=pd_zg_rss_ts_b_72_9">
        <img src="http://ecx.images-amazon.com/images/I/41Lg22K3ViL._SL160_PIsitb-sticker-arrow-dp,TopRight,12,-18_SH30_OU02_.jpg" alt="Gone Girl" border="0" hspace="0" vspace="0" />
    </a>
</div>
<span class="riRssTitle">
    <a href="http://www.amazon.co.uk/Gone-Girl-Gillian-Flynn/dp/0753827662/ref=pd_zg_rss_ts_b_72_9">Gone Girl</a>
</span>
<br />
<span class="riRssContributor">
    <a href="http://www.amazon.co.uk/Gillian-Flynn/e/B001JP3W46/ref=ntt_athr_dp_pel_1">Gillian Flynn</a>
    <span class="byLinePipe">(Author)</span>
</span>
<br />
<img src="http://g-ecx.images-amazon.com/images/G/02/x-locale/common/icons/uparrow_green_trans._V192561975_.gif" width="13" align="abstop" alt="Ranking has gone up in the past 24 hours" title="Ranking has gone up in the past 24 hours" height="11" border="0" />
<font color="green">
    <strong></strong>
</font> 674 days in the top 100 
<br />
<img src="http://g-ecx.images-amazon.com/images/G/02/detail/stars-4-0._V192253865_.gif" width="64" height="12" border="0" style="margin: 0; padding: 0;"/>(5704)
<br />
<br />
<a href="http://www.amazon.co.uk/Gone-Girl-Gillian-Flynn/dp/0753827662/ref=pd_zg_rss_ts_b_72_9">Buy new: </a>
<strike>£9.07</strike>
<font color="#990000">
    <b>£3.85</b>
</font>
<br />
<a href="http://www.amazon.co.uk/gp/offer-listing/0753827662/ref=pd_zg_rss_ts_b_72_9?ie=UTF8&condition=all">60 used & new</a> from 
<span class="price">£2.21</span>
<br />
<br />(Visit the 
<a href="http://www.amazon.co.uk/Best-Sellers-Books-Crime-Thrillers-Mystery/zgbs/books/72/ref=pd_zg_rss_ts_b_72_9">Bestsellers in Crime, Thrillers & Mystery</a> list for authoritative information on this product's current rank.)

【问题讨论】:

  • 谁能给我一些指导?
  • 有一天,有人告诉我:stackoverflow.com/questions/1732348/…
  • @JJBoursier 我昨天遇到了这个。这是一个有趣的阅读。不过,我的解决方案不需要用正则表达式来解决。
  • 如果是 XML,您是否考虑过使用 XPath?
  • 您能否使用服务器端技术(正确的 JSON/XML 将发送到设备,但必须在服务器上完成)。

标签: html ios objective-c xml parsing


【解决方案1】:

TFHpple 绝对是解析 HTML 的库。 (在 github 上>1000 颗星) https://github.com/topfunky/hpple

这是该 RSS 提要的 obj-c 解决方案:

NSString *stringURL = @"http://www.amazon.co.uk/gp/rss/bestsellers/books/72/ref=zg_bs_72_rsslink";
NSURL  *url = [NSURL URLWithString:stringURL];
NSData *htmlData = [NSData dataWithContentsOfURL:url];

TFHpple * doc = [[TFHpple alloc] initWithHTMLData:htmlData];

NSArray *titleElements = [doc searchWithXPathQuery:@"//span[@class='riRssTitle']/a"];
for (TFHppleElement *element in titleElements)
{
    NSString *title = element.firstChild.content;
    NSLog(@"title: %@", title);
}

NSArray *imageElements = [doc searchWithXPathQuery:@"//a[@class='url']/img"];
for (TFHppleElement *element in imageElements)
{
    NSString *image = element.attributes[@"src"];
    NSMutableArray *parts = [[image componentsSeparatedByString:@"/"] mutableCopy];
    NSArray *pathParts = [parts.lastObject componentsSeparatedByString:@"."];
    [parts removeLastObject];
    [parts addObject:[NSString stringWithFormat:@"%@.%@",pathParts.firstObject, pathParts.lastObject]];
    image = [parts componentsJoinedByString:@"/"];
    NSLog(@"image: %@", image);
}

NSArray *authorElements = [doc searchWithXPathQuery:@"//span[@class='riRssContributor']/a"];
for (TFHppleElement *element in authorElements)
{
    NSString *author = element.firstChild.content;
    NSLog(@"author: %@", author);
}

NSArray *priceElements = [doc searchWithXPathQuery:@"//font/b"];
for (TFHppleElement *element in priceElements)
{
    NSString *price = element.firstChild.content;
    NSLog(@"price: %@", price);
}

NSArray *ratingElements = [doc searchWithXPathQuery:@"//img"];
for (TFHppleElement *element in ratingElements)
{
    if (![element.attributes[@"src"] containsString:@"stars"])
        continue;

    NSArray *parts = [element.attributes[@"src"] componentsSeparatedByString:@"-"];
    if (parts.count < 5) continue;

    NSString *rating = [NSString stringWithFormat:@"%@.%@", parts[3], [parts[4] substringToIndex:1]];
    NSLog(@"rating: %@", rating);
}

正如您所说,您受制于亚马逊的命名约定。

【讨论】:

  • 为什么在提供路径时for循环?如果您正在搜索元素以查找特定内容,我会理解,但我看到为每个 for 循环的每次迭代分配了一个值。发生了什么事?
  • 因为您提供的 RSS 提要的链接有多个项目。 for 循环打印出每个项目的属性。
  • 那么,您只是在附和这些项目并提出作为我问题的解决方案?
  • RSS 提要中有 10 个项目。每个 for 循环都回显了您要解析的 6 个属性之一。第一个 for 循环 - 10 个书名,第二个 for 循环 - 10 个图书图片等
  • 因此,如果您查看实时提要,您会注意到价格有时会跳动,并且某些值会为作者返回 null,因为它们会跳入其他标签。没有更好的办法吗?我不能有空值。
【解决方案2】:

您可以根据需要使用TFHppleTFHppleElement 来解析上述数据。

Here 是这样做的参考。

【讨论】:

    【解决方案3】:

    我在 iOS 开发者 Facebook 群组上看到了您的帖子,并认为我会在最后一分钟提供意见。

    由于亚马逊没有严格的命名约定,因此您必须在提要中进行搜索。 这是我试图做的,但后来我试着让它看起来不那么骇人听闻。 如果您注意到,您会发现有时如果您尝试搜索特定路径名,Feed 会返回缺失值,因此我也尝试弥补这种情况。

    为此,您只需从以下 URL 下载 NSDictionary 类别:https://github.com/nicklockwood/XMLDictionary

    .h
    #import <Foundation/Foundation.h>
    
    @interface JMAmazonProcessor : NSObject
    +(NSArray*)processAmazonResponseWithXMLData:(NSData*)responseObject;
    @end
    

    为了

    .m
    
    #import "JMAmazonProcessor.h"
    
    @implementation JMAmazonProcessor
    
    
    
    +(NSString*)getBookTitleWithArray:(NSArray*)array{
        return [[array[0] objectForKey:kAmazonAHREFKey] objectForKey:kAmazonUnderscoreTextKey];
    }
    
    +(NSString*)getBookAuthorWithArray:(NSArray*)array{
        id bookAuthor = [[array[1] objectForKey:kAmazonAHREFKey] objectForKey:kAmazonUnderscoreTextKey];
    
        if(!bookAuthor){
            bookAuthor = [array[1] objectForKey:kAmazonUnderscoreTextKey];
        }
    
        if([bookAuthor isKindOfClass:[NSArray class]]){
            bookAuthor = [bookAuthor componentsJoinedByString:@" "];
        }
    
        return bookAuthor;
    }
    +(NSString*)getPriceFromDictionary:(NSDictionary*)dictionary{
        return [NSString stringWithUTF8String:[[[[dictionary objectForKey:@"font"] lastObject] objectForKey:@"b"] cStringUsingEncoding:NSUTF8StringEncoding]];
    }
    
    
    +(NSString*)getRatingWithCurrentRatingDictionary:(NSDictionary*)ratingDictionary{
        NSString * stars;
        if([ratingDictionary objectForKey:@"_src"]){
            NSString * possibleStarsURL = [ratingDictionary objectForKey:@"_src"];
            if([possibleStarsURL rangeOfString:@"stars-" options:NSCaseInsensitiveSearch].location != NSNotFound){
                stars = [[[[[possibleStarsURL componentsSeparatedByString:@"stars-"] lastObject] componentsSeparatedByString:@"."] firstObject] stringByReplacingOccurrencesOfString:@"-" withString:@"."];
            }
        }
    
    
        return stars;
    
    }
    +(NSString*)getRatingFromDictionary:(NSDictionary*)dictionary{
        id currentDictionary = [dictionary objectForKey:@"img"];
        NSString *rating;
    
        if([currentDictionary isKindOfClass:[NSArray class]]){
            for(int i = 0; i < [currentDictionary count]; i++){
                NSDictionary *currentRatingDictionary = [currentDictionary objectAtIndex:i];
    
                if((rating = [self getRatingWithCurrentRatingDictionary:currentRatingDictionary])){
                    break;
                }
            }
        }
    
        else if([currentDictionary isKindOfClass:[NSDictionary class]]){
            rating = [self getRatingWithCurrentRatingDictionary:currentDictionary];
        }
    
        if(!rating) rating = @"Rating is not currently available";
        return rating;
    }
    
    
    +(NSArray*)processAmazonResponseWithXMLData:(NSData*)responseObject{
        NSMutableArray *bookEntries = [[NSMutableArray alloc] init];
    
        NSDictionary * itemDictionary = [[NSDictionary dictionaryWithXMLData:responseObject] objectForKey:kAmazonRootNode];
        for(int i = 0; i < [[itemDictionary objectForKey:kAmazonFeedItemKey] count]; i++){
            RSSBookEntryModel *cBEO = [[RSSBookEntryModel alloc] init];
    
            NSDictionary *currentItem = [[itemDictionary objectForKey:kAmazonFeedItemKey] objectAtIndex:i];
            NSString *finalXMLString = [NSString stringWithFormat:@"%@%@%@", kAmazonStartTag, [currentItem objectForKey:kAmazonDescriptionKey], kAmazonEndTag];
            NSDictionary *cData = [NSDictionary dictionaryWithXMLString:finalXMLString];
    
            NSArray *bookDetailsDictionary = [cData objectForKey:kAmazonSpanKey];
    
            NSString *bIOURL = [[[[cData objectForKey:@"div"] objectForKey:kAmazonAHREFKey] objectForKey:@"img"] objectForKey:@"_src"];
            NSString *bookImageCoverID = [[[[bIOURL componentsSeparatedByString:kAmazonBookCoverBaseURL] lastObject] componentsSeparatedByString:@"."] firstObject];
    
    
    
    
            cBEO.bookTitle = [self getBookTitleWithArray:bookDetailsDictionary];
            cBEO.bookAuthor = [self getBookAuthorWithArray:bookDetailsDictionary];
            cBEO.bookCoverImageThumbnailURL = [NSString stringWithFormat:@"%@%@%@%@", kAmazonBookCoverBaseURL, bookImageCoverID, kAmazonBookCoverThumbnailSize, kAmazonBookCoverFileExtention];
            cBEO.bookCoverImageOriginalURL = [NSString stringWithFormat:@"%@%@%@%@", kAmazonBookCoverBaseURL, bookImageCoverID, kAmazonBookCoverMaxSize, kAmazonBookCoverFileExtention];
            cBEO.bookPrice = [self getPriceFromDictionary:cData];
            cBEO.bookRating = [self getRatingFromDictionary:cData];
    
            [bookEntries addObject:cBEO];
    
    
        }
        return bookEntries;
    }
    @end
    

    对不起。 这里是: 这是您要使用的对象模型,非常简单。

    @interface RSSBookEntryModel : NSObject
    
    @property (strong, nonatomic) NSString *bookTitle;
    @property (strong, nonatomic) NSString *bookAuthor;
    @property (strong, nonatomic) NSString *bookCoverImageThumbnailURL;
    @property (strong, nonatomic) NSString *bookCoverImageOriginalURL;
    @property (strong, nonatomic) NSData *bookCoverThumbnailImage;
    @property (strong, nonatomic) NSData *bookCoverOriginalImage;
    
    @property (strong, nonatomic) NSString *bookPrice;
    @property (strong, nonatomic) NSString *bookRating;
    
    
    -(NSString*)description;
    
    @end
    

    这是我用来保持一切清洁的常量。

    Constant.h
    
    extern NSString * const kAmazonRootNode;
    extern NSString * const kAmazonStartTag;
    extern NSString * const kAmazonEndTag;
    
    extern NSString * const kAmazonFeedItemKey;
    extern NSString *const kAmazonSpanKey;
    extern NSString * const kAmazonDescriptionKey;
    extern NSString *const kAmazonUnderscoreTextKey;
    extern NSString *const kAmazonAHREFKey;
    extern NSString *const kAmazonBookCoverBaseURL;
    
    extern NSString *const kAmazonBookCoverThumbnailSize;
    extern NSString *const kAmazonBookCoverMaxSize;
    extern NSString *const kAmazonBookCoverFileExtention;
    

    这是 Constants.m 文件。

    NSString * const kAmazonRootNode = @"channel";
    NSString * const kAmazonStartTag = @"<startTag>";
    NSString * const kAmazonEndTag = @"</startTag>";
    
    NSString * const kAmazonFeedItemKey = @"item";
    NSString *const kAmazonSpanKey = @"span";
    NSString * const kAmazonDescriptionKey = @"description";
    NSString *const kAmazonUnderscoreTextKey = @"__text";
    NSString *const kAmazonAHREFKey = @"a";
    NSString *const kAmazonBookCoverBaseURL = @"http://ecx.images-amazon.com/images/";
    
    NSString *const kAmazonBookCoverThumbnailSize = @"._SL100";
    NSString *const kAmazonBookCoverMaxSize = @"._SL500";
    NSString *const kAmazonBookCoverFileExtention = @".jpg";
    

    【讨论】:

    • 哇,好吧,只看代码,我注意到你正在覆盖的作者代码有时返回的值是null!伟大的。但是 RSSBookEntryModel 代表什么以及其中包含什么?还有常量在哪里?
    • 我很高兴你注意到了额外的处理;)
    • 您不需要向数组中输入任何内容,这些方法由主要方法使用,并且它用于您将亚马逊响应对象提供给的主要方法。其他一切都已处理完毕,然后您将从该亚马逊提要中收到一组包含十本书对象的数组。
    • 就像一个魅力,但这仍然是非常硬编码的。我把它给你是因为你处理了有时作者值会返回 null 的情况。谢谢
    【解决方案4】:

    这是一个相当弱的替代方案,但也许它会有所帮助:

    //title
    console.log("TITLE: " + $(".riRssTitle").text().trim());
    
    //image
    console.log("IMAGE: " + $(document).find("img").attr("src"));
    
    //author
    console.log("AUTHOR: " + $(".riRssContributor").find("a").text().trim());
    
    //new price and striked price
    var new_price_striked_element = $("a:contains('Buy new')").siblings("strike");
    if(new_price_striked_element){
       console.log("NEW PRICE STRIKED: " + new_price_striked_element.text().trim());     
    }else{
       console.log("NEW PRICE: " + $("a:contains('Buy new')").siblings("b").text().trim()); 
    }
    
    //used price
    console.log("USED PRICE FROM: " + $(".price").text().trim());
    
    //stars
    var url = $("img[src*='stars']").attr("src");
    var myRegexp = /stars-([0-9]-[0-9])/g;
    var match = myRegexp.exec(url);
    console.log("STARS: " + match[1]);
    

    示例:http://jsfiddle.net/qpuaxtv3/

    【讨论】:

    • 亚马逊的feed中的item节点不一致,真的很烦人。有时可以检索价格,有时则不能。不同实体没有特定的节点。我最终依赖于创建许多不同的场景,并做了很多“狩猎”,这不是我期望的解决问题的方式
    • 是的,这很弱。它适用于一种特定情况,但可能不适用于下一种情况。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-12-22
    • 1970-01-01
    • 2011-09-16
    • 1970-01-01
    • 1970-01-01
    • 2014-02-05
    • 1970-01-01
    相关资源
    最近更新 更多