OC爬虫 -- 结合正则表达式

版权声明:本文为博主原创文章,转载请注明。 https://blog.csdn.net/a44496913/article/details/58236843

一、说明

使用OC写爬虫是可以的,但其不足之处在于OC用于移动设备编程,而移动设备那小的可怜的存储空间(相对于PC),大多数场景并不适用。然,获取小量数据、做个小试验还是可以的。下面使用OC配合正则表达式获取某个网页上自己需要的内容。


二、获取  CSDN 官方频道  这个页面里面所有博文的标题和链接

进入这个页面后我们发现里面有很多模块和内容,其中就有我们需要的博文列表,那怎么每篇博文的标题和链接取出来呢,下面一步步处理:

1.打开 Firebug 插件(我用的Firefox,其他浏览器也有类似插件),可以看到页面的源码了



2.当然,在进行后面的处理前是必须先将页面 down 下来的

//获取网页数据并转为字符串
- (NSString *)htmlWithUrlString:(NSString *)urlString {
    
    urlString = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    
    NSURL *url = [NSURL URLWithString:urlString];
    
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    
    NSError *error = nil;
    
    if (error) {
        NSLog(@"%@",error.localizedDescription);
        return nil;
    }
    
    NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:NULL error:&error];
    NSString *backStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    
    return backStr;
}

3.在 <body> xxx </body> 中寻找博文列表的内容,如果不好找,可以用 Firebug  右上角搜索器搜索某一篇文章标题,会容易许多



经过查找发现,所有文章(置顶文章除外)都在 <div id="article_list" class="list"> xxx </div>之间,所以我们就可以进行第一层过滤

/**
     第一次过滤正则
     
     截取取内容符合 <div id="article_list" class="list">...</div> 格式的字符串
     */
    NSString *firstPattern = [NSString stringWithFormat:@"<div id=\"article_list\" class=\"list\">(.*)</div>"];
    
    NSString *content = [htmlStr firstMatchWithPattern:firstPattern];


4.通过上图我们还发现每篇文章的 title 和 url 都在 <span class="link_title"> <a href="/blogdevteam/article/details/xxxxxx"> xxx </a> </span> 之间,so,进行第二次过滤,获得需要的字符串,并将其转化为数组

/**
     第二次过滤正则
     
     截取取内容符合 <span class="link_title"><a href="...">...</a></span> 格式的字符串
     */
    NSString *secondPattern = @"<span class=\"link_title\"><a href=\"(.*?)\">(.*?)</a></span>";
    
    //将字符串转为数组
    NSArray *array = [content matchesWithPattern:secondPattern keys:@[@"url", @"title"]];

5.在得到文章列表数组后,却发现每篇文章的 title 前后存在空格与换行符,url 也不完整,对于强迫症的我来说这不能忍,嗯,下面就收拾他们

NSMutableArray *tempArray = [NSMutableArray array];
    
    //去除title中的 空格 和 换行
    //将url补全,方便后面使用
    for (NSDictionary *dic in array) {
        
        NSString *title = dic[@"title"];
        
        NSMutableArray *arr = [NSMutableArray arrayWithArray:[title componentsSeparatedByString:@" "]];
        [arr removeObject:@""];
        [arr removeLastObject];
        [arr removeObjectAtIndex:0];
        
        NSString *newStr = [arr componentsJoinedByString:@" "];
        NSDictionary *appDic = @{@"url":[NSString stringWithFormat:@"http://blog.csdn.net%@",dic[@"url"]],
                               @"title":newStr};
        
        [tempArray addObject:appDic];
    }

6.在获取满意的数据后,我们就可以把它放在 UITableView 上展示了。此外,为了方便使用,把数据本地化保存是个不错的选择

  • 存入本地

NSString *homePath = NSHomeDirectory();
NSString *path = [homePath stringByAppendingPathComponent:@"Library/Caches/test_cache.plist"];
    
[tempArray writeToFile:path atomically:YES];

  • 取出

- (NSArray *)getCacheArray{
    
    NSString *homePath = NSHomeDirectory();
    NSString *path = [homePath stringByAppendingPathComponent:@"Library/Caches/test_cache.plist"];
    NSArray *cacheArr = [NSArray arrayWithContentsOfFile:path];
    
    return cacheArr;
}


7.为了完成以上处理,NSString 系统类提供的方法是不足的,所以需要对其进行扩展

/** 将GBK编码的二进制数据转换成字符串(这里没有使用) */
+ (NSString *)UTF8StringWithHZGB2312Data:(NSData *)data
{
    NSStringEncoding encoding = CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);
    return [[NSString alloc] initWithData:data encoding:encoding];
}

/** 查找并返回第一个匹配的文本内容 */
- (NSString *)firstMatchWithPattern:(NSString *)pattern
{
    NSError *error = nil;
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern
                                                                           options:NSRegularExpressionCaseInsensitive | NSRegularExpressionDotMatchesLineSeparators
                                                                             error:&error];
    
    if (error) {
        NSLog(@"匹配方案错误:%@", error.localizedDescription);
        return nil;
    }
    
    NSTextCheckingResult *result = [regex firstMatchInString:self options:0 range:NSMakeRange(0, self.length)];
    
    if (result) {
        NSRange range = [result rangeAtIndex:0];
        return [self substringWithRange:range];
    } else {
        NSLog(@"没有找到匹配内容 %@", pattern);
        return nil;
    }
}

/** 查找多个匹配方案结果 */
- (NSArray *)matchesWithPattern:(NSString *)pattern
{
    NSError *error = nil;
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern
                                                                           options:NSRegularExpressionCaseInsensitive | NSRegularExpressionDotMatchesLineSeparators
                                                                             error:&error];
    
    if (error) {
        NSLog(@"匹配方案错误:%@", error.localizedDescription);
        return nil;
    }
    
    return [regex matchesInString:self options:0 range:NSMakeRange(0, self.length)];
}

/** 查找多个匹配方案结果,并根据键值数组生成对应的字典数组 */
- (NSArray *)matchesWithPattern:(NSString *)pattern keys:(NSArray *)keys
{
    NSArray *array = [self matchesWithPattern:pattern];
    
    if (array.count == 0) return nil;
    
    NSMutableArray *arrayM = [NSMutableArray array];
    
    for (NSTextCheckingResult *result in array) {
        NSMutableDictionary *dictM = [NSMutableDictionary dictionary];
        
        for (int i = 0; i < keys.count; i++) {
            NSRange range = [result rangeAtIndex:(i + 1)];
            
            [dictM setObject:[self substringWithRange:range] forKey:keys[i]];
        }
        [arrayM addObject:dictM];
    }
    
    return [arrayM copy];
}

三、补充

1.我们知道当CSDN博客每页所能呈现的文章数量是有限的,上面过程所获取的数据只是列表的第一页,也就是 :

    http://blog.csdn.net/blogdevteam

    而从第二页开始每页的链接为

    http://blog.csdn.net/blogdevteam/article/list/2

    http://blog.csdn.net/blogdevteam/article/list/3

    http://blog.csdn.net/blogdevteam/article/list/4

    ......

很明显,之后的页面链接在有规律的改变,所以我们可以用循环......

好吧,其实这里有个简单粗暴的方法 ----> 将页数直接取值 >= 该账号下文章最大分页数,如:http://blog.csdn.net/blogdevteam/article/list/999

因为,当页数 >=  最大分页数时,通过第一步下载下来的页面数据中将包含所有文章

2.上面的操作是没有包含置顶文章的,不过通过观察网页源码,我们可以发现置顶文章藏在 <div id="article_toplist" class="list"> xxx </div>中,既然知道了在哪里,那么在修改一个过滤正则,置顶文章也可以抓到手了。


最后,源码地址:  https://github.com/HuberyYang/SmallSpider.git



  

猜你喜欢

转载自blog.csdn.net/a44496913/article/details/58236843