之前在天涯论坛看到一高三老师的一篇帖子,是高三一年的记录。当时就想扣下来,虽然只分九页,但每页有百来屏,采取纯手工的方法不可取。做个工具以后还可以用!但一直没动手。
这两天突然看到《任务列表.txt》里有 这个任务记录,便开始复习python了。
高三老师日记 2014-2015 http://bbs.tianya.cn/post-no16-276224-1.shtml
整个工作是昨天下午开始的 也就是2015-08-15.因为要上班 下午5:32开始写,大概7点下班。
回来后从7:51开始到11:09结束,抓了374张图,文字1.22MB。写这个时当时一算,发现用了将近5小时,便思考值不值?
首先是对帖子每一楼的分析,发现每一楼都是在div里有id class js_username js_resttime等属性。帖子的主要内容是在div class="bbs-content"里。因为只关注楼主的帖子,通过div的js_username属性就可以把其他用户的帖子过滤掉了。关键是对这一楼是楼主的帖子还是楼主的回复的区分。楼主发帖基本是日常记录,基本没有什么闲聊。如果是回复,div.bbs-content里会有a标签而且是第一个。
帖子里可能有img标签,还有就是<br/>转'\n',不然文字没分段。resttime里有时间,因为是日记贴,也有必要抓下来。
下面就是爬虫部分了。实现上面的思路。
首先要对SGMLParseer这个模块有了解,继承这个类自己对标签进行处理。刚开始我还是对里面的函数有误解的。
这个模块用于解析HTML,SGMLParser 将 HTML 分析成 8 类数据。
- 开始标记 (Start tag)
- 结束标记 (End tag)
- 字符引用 (Character reference)
- 字符引用 (Character reference)
- 注释 (Comment)
- 处理指令 (Processing instruction)
- 声明 (Declaration)
- 文本数据 (Text data)
#coding:utf-8
import re,cookielib,urllib2,socket,random
from sgmllib import SGMLParser
#模拟浏览器获取html
class Browser(object):
def __init__(self):
#20 --> 4 节约了很多时间 爬一千个url由15.5s-->6.5s
socket.setdefaulttimeout(4)
def speak(self,content):
print '[%s]' %(content)
def openurl(self,url):
cookie_support= urllib2.HTTPCookieProcessor(cookielib.CookieJar())
self.opener = urllib2.build_opener(cookie_support,urllib2.HTTPHandler)
urllib2.install_opener(self.opener)
user_agents = [
'Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11',
'Opera/9.25 (Windows NT 5.1; U; en)',
'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.12) Gecko/20070731 Ubuntu/dapper-security Firefox/1.5.0.12',
'Lynx/2.8.5rel.1 libwww-FM/2.14 SSL-MM/1.4.1 GNUTLS/1.2.9',
"Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.7 (KHTML, like Gecko) Ubuntu/11.04 Chromium/16.0.912.77 Chrome/16.0.912.77 Safari/535.7",
"Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:10.0) Gecko/20100101 Firefox/10.0 ",
]
agent = random.choice(user_agents)
self.opener.addheaders = [("User-agent",agent),("Accept","*/*"),('Referer','http://www.baidu.com')];
#self.opener.addheaders = [("User-agent",agent),("Accept","*/*"),('Referer','http://bbs.tianya.cn/post-no04-2185536-1.shtml')];
res='';
err=[];
try:
res = self.opener.open(url)
#print res.read()
except Exception,e:
#self.speak(str(e)+url)
#对于因为网络不稳定带来的错误 应当 再获取两次 减少误差
print '获取网页失败,尝试重新获取';
if len(err)!=0:
try:
res = self.opener.open(url);
print '获取成功!';
except Exception,e:
print '尝试获取网页失败!';
#raise Exception
return res;
def getHTML(self,url):
ress='';
restemp = self.openurl(url);
try:
if restemp!=None and restemp!='':
ress = restemp.read();
else:
ress = '';
except Exception,e:
print 'read error!';
return ress;
#用于获取图片地址
def getFileName(filestr):
m1=re.compile(r'(?P<path>[0-9_A-Za-z-]+(.jpg|.png|.gif)$)')
extName=m1.search(filestr)
if extName!=None:
return extName.group('path')
else:
return '';
#getURLs从SGMLParser继承
class getURLs(SGMLParser):
def reset(self):
self.bItem=False;
self.bContent=False;
self.id=0;
self.time='';
self.content='';
SGMLParser.reset(self);
def start_div(self,attrs):
temptime='';
tempid='';
for k,v in attrs :
if k=='js_username' and v=='%E7%A6%85%E9%A6%99%E9%9B%AA':
self.bItem=True;
if k=='js_restime':
temptime=v;
if k=='id':
tempid=v;
if k=='class' and v=='bbs-content':
self.bContent=True;
if self.bItem:
self.time=temptime;
self.id=tempid;
self.bItem=False;
def start_br(self,attrs):
self.content+='\n\t';
def start_img(self,attrs):
src=[v for k,v in attrs if k=='original'];
if src:
print src;
filename=getFileName(src[0]);
self.content+='\n\t';
self.content=self.content+'<img src="images/'+filename+'" style="width:80%;"/>';
self.content+='\n\t';
def end_div(self):
if self.bContent:
self.bContent=False;
fh.write('{"id":"'+str(self.id)+'",'+'"time":"'+self.time+'",'+'"content":"'+self.content.replace('"',"'").strip()+'"},\n');
arrItem.append('0');
print str(self.id)+'\n';
self.id='id';
self.time='time';
self.content='';
def handle_data(self,text):#处理文本
if self.bContent==True:
self.content+=text;
if __name__ == '__main__':
#===================全局变量==============================
#入口地址
entrance='http://bbs.tianya.cn/post-no16-276224-1.shtml';
#分页页面地址规则
rule = 'http://bbs.tianya.cn/post-no16-276224-\d.shtml';
arrPage=[entrance];
arrItem=range(0);
curr=1;
id=0;
time='';
#分页地址匹配
m = re.compile(r'(?P<path>'+rule+')');
fh = open("f:\\test\\recorder.txt",'a+');
#====================进入入口===========================
spider = Browser();
html = spider.getHTML(entrance);
obj = getURLs();
obj.feed(html);
obj.close();
#开始循环
for i in range(2,10):
html = spider.getHTML('http://bbs.tianya.cn/post-no16-276224-'+str(i)+'.shtml');
obj = getURLs();
obj.feed(html);
obj.close;
print '第'+str(curr)+'页';
curr=curr+1;
print '完毕!';
fh.close();
就是这段程序完成了抓取整个帖子和图片。但没有对发帖和回复的区分。输出的还是json格式。最后会多个逗号。
当时没考虑传参。移植性差。
class getURLs(SGMLParser)这句是说getURLs是继承于SGMLParser
今天打算写个模块,可以传参,发现__init__和reset两个函数始终有矛盾。后来将reset里的都放到__init__构造函数里了。
既然是模块,当然希望按引用传参。然而int和string类型只能按值传参。参数写成数组的形式就可以按引用传参了。
另外发现了个关于定义空数组的问题。arr=[];是可以,但这种方法arr.append(xxx);始终没效果。第二种方法是arr=range(0);
期间发现了两个有趣的东西。href=[v for k,v in attrs if k=='href'];和 value in Array==True判断数组是否有变量。然而第二个我没用,换成了自己的函数arrHas(a,arr)
还有问题,就是HTML里面div嵌套非常多,像<div>aaa<p>bbb</p>ccc</div>要获取里面的文本就麻烦一些。一般方法是在构造函数里加个变量self.isDiv=False;
在def start_div(self,attrs):self.isDiv=True;在def end_div(self): self.isdiv=False;
而帖子里每楼都有5、6div嵌套的。还要加个self.divDepth=-1。用于记录嵌套的深度。start_div就自增,end_div时就自减。
后来写了个获取网页链接的模块
#coding:utf-8
'''
auto:阮家友
time:2015-08-16 14:22
'''
import re,cookielib,urllib2,socket,random,urllib,urlparse
from sgmllib import SGMLParser
class Browser(object):
def __init__(self):
#20 --> 4 节约了很多时间 爬一千个url由15.5s-->6.5s
socket.setdefaulttimeout(4)
def speak(self,content):
print '[%s]' %(content)
def openurl(self,url):
cookie_support= urllib2.HTTPCookieProcessor(cookielib.CookieJar())
self.opener = urllib2.build_opener(cookie_support,urllib2.HTTPHandler)
urllib2.install_opener(self.opener)
user_agents = [
'Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11',
'Opera/9.25 (Windows NT 5.1; U; en)',
'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.12) Gecko/20070731 Ubuntu/dapper-security Firefox/1.5.0.12',
'Lynx/2.8.5rel.1 libwww-FM/2.14 SSL-MM/1.4.1 GNUTLS/1.2.9',
"Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.7 (KHTML, like Gecko) Ubuntu/11.04 Chromium/16.0.912.77 Chrome/16.0.912.77 Safari/535.7",
"Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:10.0) Gecko/20100101 Firefox/10.0 ",
]
agent = random.choice(user_agents)
self.opener.addheaders = [("User-agent",agent),("Accept","*/*"),('Referer','http://www.baidu.com')];
#self.opener.addheaders = [("User-agent",agent),("Accept","*/*"),('Referer','http://bbs.tianya.cn/post-no04-2185536-1.shtml')];
res='';
err=[];
try:
res = self.opener.open(url);
except Exception,e:
#self.speak(str(e)+url)
#对于因为网络不稳定带来的错误 应当 再获取1次 减少误差
print '获取网页失败,尝试重新获取';
if len(err)!=0:
try:
res = self.opener.open(url);
print '获取成功!';
except Exception,e:
print '尝试获取网页失败!';
print url;
#raise Exception
return res;
def getHTML(self,url):
ress='';
restemp = self.openurl(url);
try:
if restemp!=None and restemp!='':
ress = restemp.read();
else:
ress = '';
except Exception,e:
print 'read error!';
return ress;
def arrHas(strArr,string):
for i in range(0,len(strArr)):
if(strArr[i]==string):
return True;
return False;
class getURLs(SGMLParser):
def __init__(self,args):
self.len=0;
self.url = args[0][0];
self.arrPage=args[0];
self.arrDest=args[1];
self.pageRule = args[2];
self.DestRule = args[3];
SGMLParser.reset(self);
spider = Browser();
html=spider.getHTML(self.url);
self.url=urlparse.urlparse(self.url);
pathinfo = self.url.path.split('/');
if len(pathinfo)>0 and pathinfo[len(pathinfo)-1].find('.')!=-1:
pathinfo.pop();
self.url.path = pathinfo.join('/');
self.feed(html);
def start_a(self,attrs):
#print attrs;
href=[v for k,v in attrs if k=='href'];
if href:
tempurl = urlparse.urlparse(href[0]);
if(tempurl.netloc==''):
temppathinfo=tempurl.path.split('/');
pathstr = '/'.join(temppathinfo);
pathstr = pathstr.lstrip('/');
href[0]='http://'+self.url.netloc+self.url.path+pathstr;
#=========分页地址===================
r = self.pageRule.search(href[0]);
if r!=None and arrHas(self.arrPage,href[0])==False:
self.arrPage.append(href[0]);
#=========终页地址===================
r = self.DestRule.search(href[0]);
if r!=None and arrHas(self.arrPage,href[0])==False :
self.arrDest.append(href[0]);
print href[0];
return ;
def __del__(self):
print '析构函数将执行';
self.close();
if __name__ == '__main__':
#进行参数构造
entrance = 'http://h.7k7k.com/pc/';
m1 = re.compile(r'(?P<path>http://h.7k7k.com/pc/\d+)');
m2 = re.compile(r'(?P<path>http://flash1.7k7k.com/h5/)');
arrPage=[entrance];
arrDest=range(0);
arguments = [arrPage,arrDest,m1,m2];
print arguments;
o1 = getURLs(arguments);
del o1;
print 'list page url:'
for i in range(0,len(arrPage)):
print arrPage[i];
print 'destiny page url:'
for j in range(0,len(arrDest)):
print arrDest[j];
#del o1;
print 'url 全部获取!';
这个获取了所有的游戏分页地址和游戏页面地址。
还写了个模块,专门获取页面文章(或小说),跟上面差不多,测试了一下将笔趣阁的星战风暴1130章全都down了下来。
实习的另外两个同学想玩玩,做小说站、游戏站和APP壁纸站。扣小说、游戏、图片。你懂的
我只是默默学习编程。