23hh小说网——爬虫1.0python

修改点:

1. decode('gbk')修改为decode('gbk','replace'),在遇到不能识别的字符时直接用?替换

2. 将原来的单线程变为多线程,现在处理速度快了好多

  1 #! /bin/python
  2 # -*- coding:utf-8 -*-
  3 
  4 # --------------------------------------------
  5 #     程序:【23hh小说网】爬虫
  6 #     版本:0.2.2
  7 #     作者:Silence
  8 #     日期:2014-04-08
  9 #     功能:1. 提供一个目录页,把这个小说的全部章节都抓取下来,保存为一个文件
 10 #         2. 提供一个正在看的目录页,把这个章节及以后的所有章节都抓取下来,
 11 #          3. 增加错误时的重试机制
 12 # ---------------------------------------------
 13 import threading
 14 import urllib2
 15 import re
 16 import os
 17 from Queue import Queue
 18 
 19 class Spider_Thread(threading.Thread):
 20     """单进程的爬网页很耗时的,所以考虑做成多进程的
 21         参数通过queue来传递"""
 22 
 23     def __init__(self, t_name,queue):
 24         threading.Thread.__init__(self ,name = t_name)
 25         self.data = queue
 26         self.errorInfo = {}
 27 
 28     def run(self):
 29         while self.data.qsize() > 0:
 30             pageInfo = self.data.get()
 31             print '线程%s正在爬第%d个页面'%(self.getName(),pageInfo.keys()[0])
 32             try:
 33                 self.novel = Novel_Tool(pageInfo.values()[0]['pageurl'],'N')
 34                 decodePageResp = self.novel.getDecodePage(pageInfo.values()[0]['pageurl'])
 35                 pageContent = self.novel.getPageContent(decodePageResp)
 36                 self.novel.writeToFile(pageContent,pageInfo.values()[0]['pagename'])
 37             except Exception,e:
 38                 print '爬第%d个页面时出错了' %pageInfo.keys()[0]
 39                 self.errorInfo[pageInfo.keys()[0]] = pageInfo.values()
 40                 pass
 41         if self.errorInfo.__len__() > 0:
 42             print '出错的页面信息有:
',self.errorInfo
 43         self.novel = None
 44 
 45         
 46 # 主要是用来提供一些公用的方法,不作为主函数调用
 47 class Novel_Tool():
 48 
 49     def __init__(self,weburl,saveAsOne):
 50         self.url = weburl
 51         self.headers = {
 52             'User-Agent':'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6'
 53         }
 54         self.saveAsOne = saveAsOne
 55         self.pagesInfo = {}
 56         self.errorPage = []
 57 
 58     #获取当前页面的编码格式,现在某些小说网喜欢用gbk来编码
 59     # 但是,也有一些不厚道的网站,他们的实际编码格式不是在页面中charset中定义的格式,暂时先忽略了
 60     def getPageType(self,content):
 61         pattern = re.compile('charset=.*?"')
 62         pagetype = pattern.search(content).group()
 63         pagetype = pagetype[8:len(pagetype) - 1]
 64         return pagetype
 65 
 66     def start(self):
 67         if novelUrl.find('html') > 0:
 68             self.spiderPagesFromCurrent()
 69         else:
 70             pageInfos = self.getAllUrlsAndNames()
 71             self.spiderAllPagesFromOne(pageInfos)
 72         self.doStat()
 73 
 74     def doStat(self):
 75         print '本次共尝试爬章节 %d,其中爬成功章节数 %d' %(self.pageInfos.__len__(),self.pageInfos.__len__() - self.errorPage.__len__())
 76         print '失败的章节信息为:',errorPage
 77 
 78     def retryErroePage(self,errorPages):
 79         print '准备重试错误页面中....'
 80         self.spiderAllPagesFromOne(errorPages)
 81 
 82     def spiderPagesFromCurrent(self):
 83         pageurl = self.url
 84         index = 1
 85         while pageurl.find('index.html') == -1:
 86             try:
 87                 decodePageResp = self.getDecodePage(pageurl)
 88                 pageContent = self.getPageContent(decodePageResp)
 89 
 90                 self.writeToFile(pageContent,self.getPageTitle(decodePageResp))
 91                 pageurl = self.getNextPage(decodePageResp)
 92             except Exception,e:
 93                 print '爬第%d个页面时出错了' %index
 94                 self.errorPage = {index:pageInfo['pageurl']}
 95                 pass
 96             finally:
 97                 index = index + 1
 98 
 99     # 依次遍历所有的章节,并爬下来
100     def spiderAllPagesFromOne(self,pageInfo):
101         for index,pageInfo in pageInfo.items():
102             print '正在爬第%d个页面……'%index
103             try:
104                 decodePageResp = self.getDecodePage(pageInfo['pageurl'])
105                 pageContent = self.getPageContent(decodePageResp)
106                 self.writeToFile(pageContent,pageInfo['pagename'])
107             except Exception,e:
108                 print '爬第%d个页面时出错了' %index
109                 self.errorPage = {index:pageInfo['pageurl']}
110                 pass
111 
112     # 获取正文的标题            
113     def getPageTitle(self,content):
114         charToTitleRex = re.compile('h1>(.|s)*?</h1')
115         pageTitle = charToTitleRex.search(content).group()
116         pageTitle = pageTitle[3:len(pageTitle)-4]
117         return pageTitle
118 
119     def writeToFile(self,content,filename):
120         if os.path.exists(os.getcwd() + '/Novels'):
121             if not os.path.isdir(os.getcwd() + '/Novels'):
122                 os.rename('Novels','Novels.bak')
123                 os.mkdir(os.getcwd() + '/Novels')
124         else:
125             os.mkdir(os.getcwd() + '/Novels')
126 
127         if self.saveAsOne == 'N':
128             ofile = open(os.getcwd() + '/Novels/' + filename,'w')
129         else:
130             ofile = open(os.getcwd() + '/Novels/novel.txt','a')
131 
132         try:
133             ofile.write(content)
134         except Exception, e:
135             print '存储网页',filename,'出错!'
136             pass
137         finally:
138             ofile.close()
139     
140     def getDecodePage(self,pageurl):
141         req = urllib2.Request(
142             url = pageurl,
143             headers = self.headers
144             )
145         # print pageInfo['pageurl']
146         pageResponse = urllib2.urlopen(req).read()
147         decodePageResp = pageResponse.decode(self.getPageType(pageResponse),'replace').encode('utf-8')
148         return decodePageResp
149 
150         # 章节内容
151 
152     def getPageContent(self,decodePageResp):
153         contentPattern = re.compile('(<dd id="contents">)((.|s)*?)(</dd>)')
154         content = contentPattern.search(decodePageResp).group(2)
155         content = self.replaceWebTag(content)
156         return content    
157 
158     # 获取下一页的地址
159     def getNextPage(self,content):
160         # 先获取到下一页的位置
161         footlinkRex = re.compile('(footlink">)(.*?)</dd>')
162         foot = footlinkRex.search(content).group(2)
163         pattern = re.compile(r'(返回目录.*?(<a.*?">下一页))')
164         m = pattern.search(foot).groups()
165         nextUrl = m[len(m)-1][9:m[len(m)-1].find('">')]
166 
167         return self.url[0:self.url.rfind('/')+1] + nextUrl
168 
169     def getAllUrlsAndNames(self):
170         # 先请求目录页,获取所有的目录章节和链接
171         req = urllib2.Request(
172             url = self.url,
173             headers = self.headers
174         )
175         myResponse = urllib2.urlopen(req).read()
176         decodeResp = myResponse.decode(self.getPageType(myResponse)).encode('utf-8')
177 
178         print '正在分析目录页面,请稍后…………'
179         pageRex = re.compile('<a href=".*?</td>') #定义获取所有章节页面链接的正则
180         pageUrlRex = re.compile('".*?"')    #获取章节url的正则
181         pageNameRex = re.compile('>.*?<')    #获取章节名字的正则
182 
183         pages = pageRex.findall(decodeResp)
184         index = 1
185         for page in pages:
186             pageurl = pageUrlRex.search(page).group()
187             pageurl = pageurl[1:len(pageurl) - 1]
188             pageurl = self.url + pageurl
189 
190             pagename = pageNameRex.search(page).group()
191             pagename = pagename[1:len(pagename) - 1]
192 
193             # print pagename + '     ' + pageurl
194             self.pagesInfo[index] = {
195                 'pagename' : pagename,
196                 'pageurl' : pageurl
197             }
198             index = index + 1
199         print '目录页分析完成!该小说共有%d个章节'%index
200         return self.pagesInfo
201 
202     def getNovelName(self,content):
203         titleRex = re.compile('<h1>.*?</h1>')
204         title = titleRex.search(content).group()
205         return title[4:len(title) - 5]
206 
207     def replaceWebTag(self,content):
208         charToNoneRex = re.compile(r'&nbsp;')
209         charToNewLineRex = re.compile("<br />|<br>|<br/>")
210 
211         content = charToNoneRex.sub("",content)
212         content = charToNewLineRex.sub("
",content)
213         return content
214 
215 if __name__ == '__main__':
216     print u"""
217 # * # * # * # * # * # * # * # * # * # * # * # * # * # * # * # * # * # * # * # * #
218 #     程序:【23hh小说网】爬虫                                                        #
219 #     版本:1.0                                                                    #
220 #     作者:Silence                                                                #
221 #     日期:2014-04-08                                                                #
222 #     操作:启动后输入要爬得小说目录页地址,就可以自动爬了                                #
223 #     功能:1. 提供一个目录页,把目录页中所有的目录章节都抓出来(默认是:23hh的争霸天下);    #
224 #         2. 提供一个正在看的目录页,把这个章节及以后的所有章节都抓取下来,                #
225 #         分章节保存在启动脚本目录下的Novels目录下;                                    #
226 #         如果该目录下已经有一个Novels,则把这个Novels改名为Novels.bak                    #
227 # * # * # * # * # * # * # * # * # * # * # * # * # * # * # * # * # * # * # * # * #"""
228 
229     novelUrl = raw_input('请输入要爬的小说地址(默认是:23hh的争霸天下章节目录)
')
230     if novelUrl == '':
231         novelUrl = 'http://www.23hh.com/book/43/43957/'
232     elif novelUrl.find('html') > 0:
233         novelUrl = novelUrl
234 
235     saveAsOne = raw_input('是否保存为一个文件?是为Y,否为N
')
236     if saveAsOne not in ['Y','N']:
237         saveAsOne = 'N'
238 
239     Novel = Novel_Tool(novelUrl,saveAsOne)
240 
241     if not novelUrl.find('html') > 0:
242         queue = Queue()
243         pageInfos = Novel.getAllUrlsAndNames()
244         for key,value in pageInfos.items():
245             queue.put({key:value})
246 
247         thread1 = Spider_Thread('thread1',queue)
248         thread2 = Spider_Thread('thread2',queue)
249         thread3 = Spider_Thread('thread3',queue)
250         thread4 = Spider_Thread('thread4',queue)
251         thread1.start()
252         thread2.start()
253         thread3.start()
254         thread4.start()
255     else:
256         Novel.start()
原文地址:https://www.cnblogs.com/SilenceCity/p/3653248.html