最近工作中需要一个常用字集合,网上找了些,都不太满意,所以打算自己做一个。统计常用字最难的地方就是数据收集这块,想到后台一直在网上爬东西,觉得可以利用一下,这样最难的问题就解决了。
废话少说,整体流程如下:
1.使用爬虫从网上抓取海量网页数据,采用utf-16格式保存成文件。
2.统计文件中每个字的次数,排序,生成一个log文件,该文件输出了每个字及其出现的次数,另一个文件就是常用字库啦。
一、抓取网页
采用scrapy抓取网页,这是一个开源的python库,简单易用,扩展性强,虽然我对html啥的不太熟,但还是凑活完成功能。该库的用法网上有介绍,但是个人觉得官网的介绍最给力了。在抓取网页中,主要碰到了3个问题:初始url怎么选、网页编码如何确定、重复url如何去除。
1. 初始url选择:主要看你关注哪个方面的常用字了,我的方法很简单,在百度上搜关键字,然后把链接作为初始url。
2.网页编码如何确定:网上说的方法主要有3种:一是从response对象获取编码方式,二是从网页的meta中的charset获得编码,三是采用codedet库自动分析得到,我采用第三个,因为觉得它最简单也最靠谱。
3.重复url如何去除:由于抓取的链接可能会有互相引用,导致出现重复的url,这样对常用字的统计有干扰,因此需要去掉。scrapy自带了去重的功能,但是默认不启用,不知道是出于什么考虑,启用的方法是重载爬虫的一个方法就好。
下面试爬虫的关键代码:
#coding=utf-8
from scrapy.contrib.spiders import CrawlSpider, Rule
from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
from scrapy.selector import HtmlXPathSelector
from scrapy.item import Item
from datetime import datetime
import chardet
import codecs
from scrapy.http import Request
g_cur_time = datetime.now()
g_html_fp = codecs.open('pages\\' + str(g_cur_time).replace(':', '.'), 'wb', 'utf-16')
class KeySpider(CrawlSpider):
name = 'keywords'
rules = (
Rule(SgmlLinkExtractor(), 'parse_page', follow = True),#获得子网页的url接着往下爬,主要是参数follow,每抓到一个网页都会调用parse_page函数
)
start_urls = [
#电脑
r'http://www.baidu.com/s?tn=baiduhome_pg&ie=utf-8&bs=%E5%B9%BF%E5%91%8A&f=8&rsv_bp=1&rsv_spt=1&wd=%E7%BE%8E%E5%A5%B3&rsv_sug3=17&rsv_sug=0&rsv_sug1=11&rsv_sug4=5748&inputT=26359',
#淘宝
r'http://www.baidu.com/s?wd=%E6%B7%98%E5%AE%9D&rsv_spt=1&issp=1&rsv_bp=0&ie=utf-8&tn=baiduhome_pg&rsv_sug3=4&rsv_sug=0&rsv_sug1=2&rsv_sug4=518',
#腾讯
r'http://www.baidu.com/s?tn=baiduhome_pg&ie=utf-8&bs=%E9%BB%91%E5%AE%A2&f=8&rsv_bp=1&rsv_spt=1&wd=%E8%85%BE%E8%AE%AF&rsv_sug3=7&rsv_sug=0&rsv_sug1=6&rsv_sug4=237&inputT=3017',
#游戏
r'http://www.baidu.com/s?wd=%E6%B8%B8%E6%88%8F&rsv_spt=1&issp=1&rsv_bp=0&ie=utf-8&tn=baiduhome_pg&rsv_sug3=5&rsv_sug=0&rsv_sug1=3&rsv_sug4=708',
#外挂
r'http://www.baidu.com/s?tn=baiduhome_pg&ie=utf-8&bs=%E5%A4%96%E6%8C%82&f=8&rsv_bp=1&rsv_spt=1&wd=%E5%A4%96%E6%8C%82&rsv_sug3=6&rsv_sug=1&rsv_sug1=4&rsv_sug4=306&inputT=2124',
#网购
r'http://www.baidu.com/s?tn=baiduhome_pg&ie=utf-8&bs=%E9%BB%91%E5%AE%A2&f=8&rsv_bp=1&rsv_spt=1&wd=%E6%81%B6%E6%84%8F%E7%BD%91%E7%AB%99&rsv_sug3=22&rsv_sug=0&rsv_sug1=13&rsv_sug4=1033&inputT=12260',
#美女
r'http://www.baidu.com/s?tn=baiduhome_pg&ie=utf-8&bs=%E6%81%B6%E6%84%8F%E7%BD%91%E7%AB%99&f=8&rsv_bp=1&rsv_spt=1&wd=%E7%BD%91%E6%B8%B8&rsv_sug3=4&rsv_sug=0&rsv_sug1=3&rsv_sug4=228&inputT=2177',
]
def parse_page(self, response):
global g_html_fp
cs = chardet.detect(response.body)#检测编码,保存
content = response.body.decode(cs['encoding'], 'ignore')
g_html_fp.write(content)
def make_requests_from_url(self, url):
return Request(url, dont_filter=False)#scrapy的为True,然后就不会去重了,这里我们改为False
然后就想到了怎么结束了,不能一直爬下去啊,找了一下其实是参数DEPTH_LIMIT 来决定的,它控制爬虫走的深度,这个参数写到settings.py文件中,表示爬几层。默认为0,表示无限制。
我用上面的初始网址爬了一夜都没爬完,最后强关,生成了一个6G的文件,伤不起啊,数据太多了。
二、统计常用字
这个就简单了,刚开始打算用python写的,那个难受啊,各种查文档,后来改用C++,世界都清净了。
// libgencommonword.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <locale.h>
#include <map>
#include <algorithm>
#include <vector>
using std::vector;
typedef std::pair<unsigned short int, __int64> WordPair;
void GeneratorLog(vector<WordPair>& data){
FILE* fp = NULL;
if(fopen_s(&fp, "charset.log", "w")){
printf("create log file error\n");
return;
}
std::for_each(data.begin(), data.end(), [&](const WordPair& item){
wchar_t buffer[2] = {item.first};
char buffer_mbc[5];
if(static_cast<size_t>(-1) == wcstombs(buffer_mbc, buffer, 5))
strcpy_s(buffer_mbc, "null");
fprintf(fp, "%04X,%s,%I64d\r\n", item.first, buffer_mbc, item.second);
});
fclose(fp);
}
void GenerateLib(vector<WordPair>& data){
FILE* fp = NULL;
if(fopen_s(&fp, "charsetlib.dat", "wb")){
printf("create lib file error.\n");
return;
}
std::for_each(data.begin(), data.end(), [&](const WordPair& item){
fwrite(&item.first, sizeof(item.first), 1, fp);
});
fclose(fp);
}
int _tmain(int argc, _TCHAR* argv[])
{
setlocale(LC_ALL,"");
__int64 word_count[65536] = {0};
FILE* fp = NULL;
if(argc < 2){
printf("Usage:this.exe filename\n");
return -1;
}
HANDLE hfile = CreateFile(argv[1],
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
0, NULL);
if(INVALID_HANDLE_VALUE == hfile){
printf("open file error:%s\n", argv[1]);
return -1;
}
unsigned short int ch[2048];
DWORD readed;
while(ReadFile(hfile, ch, sizeof(ch), &readed, NULL)){
if(!readed)
break;
readed /= sizeof(ch[0]);
for(DWORD i = 0; i < readed; i++){
if(!iswgraph(ch[i]) && ch[i] != 0x20)
continue;
word_count[ch[i]] += 1;
}
}
CloseHandle(hfile);
vector<WordPair> count_vec;
for(size_t i = 0; i < sizeof(word_count)/sizeof(word_count[0]); i++){
if(word_count[i])
count_vec.push_back(WordPair(i, word_count[i]));
}
std::sort(count_vec.begin(), count_vec.end(), [](WordPair& item1, WordPair& item2){
return item1.second > item2.second;
});
GenerateLib(count_vec);
GeneratorLog(count_vec);
return 0;
}
代码运行完生成两个文件,一个是常用字库,另一个是统计结果,6G的文件还是要跑一会的,估计得几分钟。
参考了不少别人的文章,在此表示感谢,如下一些链接可能对你有帮助:
字符转码的网站:http://bianma.51240.com/
scrapy去重,分析的很好:http://blog.pluskid.org/?p=381
scrapy的官网:http://doc.scrapy.org/en/0.18/
utf-16的编码:http://www.fileformat.info/info/charset/UTF-16/list.htm,http://www.fileformat.info/info/charset/index.htm