一、说明
二、获取 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