一、说明
自从成为一只猿,脑子里面总会有些奇奇怪怪的想法,例如,最近想写爬虫玩玩。一开始打算用Python写,但奈何Python功力不足,还需刻苦修炼;于是乎,退而求其次使用OC来暂时满足一下。
使用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
新闻热点
疑难解答