Python爬虫(图片)编写过程中遇到的问题
最近我突然对网络爬虫开窍了,真正做起来的时候发现并不算太难,都怪我以前有点懒,不过近两年编写了一些程序,手感积累了一些肯定也是因素,总之,还是惭愧了。好了,说正题,我把这两天做爬虫的过程中遇到的问题总结一下:
需求:做一个爬虫,爬取一个网站上所有的图片(只爬大图,小图标就略过)
思路:1、获取网站入口,这个入口网页上有很多图片集合入口,进入这些图片集合就能看到图片链接了,所以爬取的深度为2,比较简单;2、各个子图片集合内所包含的图片链接有两种形式:一种是绝对图片路径(直接下载即可),另一种的相对图片路径(需要自行拼接当前网页路径)。总之这些子图集合的表现形式简单,没有考虑更复杂的情况。3、在爬取的过程中保存已成功爬取的路径,防止每次爬取都执行重复的任务,当然,当每次启动时要首先加载该历史数据库,剩下的就是细节了。
快速链接:
一、全部代码
直接先来代码,再详细说优化的过程和内容吧:
1 __author__ = 'KLH' 2 # -*- coding:utf-8 -*- 3 4 import urllib 5 import urllib2 6 import chardet 7 import re 8 import os 9 import time 10 from myLogger import * 11 12 # 网络蜘蛛 13 class Spider: 14 15 # 类初始化 16 def __init__(self): 17 self.contentFolder = u"抓取内容" 18 self.dbName = "url.db" 19 self.createFolder(self.contentFolder) 20 self.urlDB = set() 21 22 # 获取URL数据库以获取爬过的网页地址 23 def loadDatabase(self): 24 isExists = os.path.exists(self.dbName) 25 if not isExists: 26 logging.info(u"创建URL数据库文件:'" + self.dbName + u"'") 27 f = open(self.dbName, 'w+') 28 f.write("#Create time: " + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) + ' ') 29 f.close() 30 return 31 db = open(self.dbName, 'r') 32 for line in db.readlines(): 33 if not line.startswith('#'): 34 self.urlDB.add(line.strip(' ')) 35 db.close() 36 logging.info(u"URL数据库加载完成!") 37 38 # 追加数据库文件 39 def writeToDatabase(self, url): 40 db = open(self.dbName, 'a') 41 db.write(url + ' ') 42 db.close() 43 44 # 处理路径名称中的空格字符 45 def getPathName(self, pathName): 46 newName = "" 47 subName = pathName.split() 48 i = 0 49 while i < len(subName) - 1: 50 newName = newName + subName[i] 51 i = i + 1 52 return newName 53 54 # 获取索引页面的内容 55 def getPage(self, pageURL, second): 56 try: 57 headers = {'User-agent' : 'Mozilla/5.0 (Windows NT 6.2; WOW64; rv:22.0) Gecko/20100101 Firefox/22.0'} 58 request = urllib2.Request(pageURL, headers = headers) 59 response = urllib2.urlopen(request, timeout = second) 60 data = response.read() 61 response.close() 62 return data.decode('gbk'), True 63 except urllib2.HTTPError,e: #HTTPError必须排在URLError的前面 64 logging.error("HTTPError code:" + str(e.code) + " - Content:" + e.read()) 65 return "", False 66 except urllib2.URLError, e: 67 logging.error("URLError reason:" + str(e.reason) + " - " + str(e)) 68 return "", False 69 except Exception, e: 70 logging.error(u"获取网页失败:" + str(e)) 71 return "", False 72 73 # 获取索引界面所有子页面信息,list格式 74 def getContents(self, pageURL, second): 75 contents = [] 76 page, succeed = self.getPage(pageURL, second) 77 if succeed: 78 # 这里的正则表达式很重要,决定了第一步的抓取内容: 79 pattern = re.compile('<tr>.*?<a href="(.*?)".*?<b>(.*?)</b>.*?</tr>',re.S) 80 items = re.findall(pattern,page) 81 for item in items: 82 contents.append([item[0],item[1]]) 83 contents.sort() 84 return contents 85 86 # 获取页面所有图片 87 def getAllImgURL(self, infoURL): 88 images = [] 89 succeed = True 90 try: 91 headers = {'User-agent' : 'Mozilla/5.0 (Windows NT 6.2; WOW64; rv:22.0) Gecko/20100101 Firefox/22.0'} 92 request = urllib2.Request(infoURL, headers = headers) 93 data = urllib2.urlopen(request).read() 94 chardet1 = chardet.detect(data) # 自动判断网页编码 95 page = data.decode(str(chardet1['encoding'])) 96 97 # 第一种解码格式: 98 pattern = re.compile('<option value="(.*?)">(.*?)</option>') 99 items = re.findall(pattern, page) 100 # item[0]为图片URL尾部,item[1]为图片名称 101 for item in items: 102 if item.startswith('http://'): 103 imageURL = item[0] 104 if imageURL in self.urlDB: 105 logging.info(u"获得图片URL(曾被访问,跳过):" + imageURL) 106 else: 107 logging.info(u"获得图片URL:" + imageURL) 108 images.append(imageURL) 109 else: 110 imageURL = infoURL + item[0] 111 if imageURL in self.urlDB: 112 logging.info(u"获得图片URL(曾被访问,跳过):" + imageURL) 113 else: 114 logging.info(u"获得图片URL:" + imageURL) 115 images.append(imageURL) 116 117 # 第二种解码格式 118 pattern = re.compile('<IMG src="(.*?)".*?>') 119 items = re.findall(pattern, page) 120 # item为图片URL 121 for item in items: 122 if item.startswith('http://'): 123 if item in self.urlDB: 124 logging.info(u"获得图片URL(曾被访问,跳过):" + item) 125 else: 126 logging.info(u"获得图片URL:" + item) 127 images.append(item) 128 129 except Exception, e: 130 logging.warning(u"在获取子路径图片列表时出现异常:" + str(e)) 131 succeed = False 132 return images, succeed 133 134 # 保存所有图片 135 def saveImgs(self, images, name): 136 logging.info(u'发现"' + name + u'"共有' + str(len(images)) + u"张照片") 137 allSucceed = True 138 for imageURL in images: 139 splitPath = imageURL.split('/') 140 fTail = splitPath.pop() 141 fileName = name + "/" + fTail 142 logging.info(u"开始准备保存图片(超时设置:120秒):" + imageURL) 143 startTime = time.time() 144 succeed = self.saveImg(imageURL, fileName, 120) 145 spanTime = time.time() - startTime 146 if succeed: 147 logging.info(u"保存图片完成(耗时:" + str(spanTime) + u"秒):" + fileName) 148 # 保存文件存储记录 149 self.urlDB.add(imageURL) 150 self.writeToDatabase(imageURL) 151 else: 152 logging.warning(u"保存图片失败(耗时:" + str(spanTime) + u"秒):" + imageURL) 153 allSucceed = False 154 # 为了防止网站封杀,这里暂停1秒 155 time.sleep(1) 156 return allSucceed 157 158 # 传入图片地址,文件名,超时时间,保存单张图片 159 def saveImg(self, imageURL, fileName, second): 160 try: 161 headers = {'User-agent' : 'Mozilla/5.0 (Windows NT 6.2; WOW64; rv:22.0) Gecko/20100101 Firefox/22.0'} 162 request = urllib2.Request(imageURL, headers = headers) 163 u = urllib2.urlopen(request, timeout = second) 164 data = u.read() 165 f = open(fileName, 'wb') 166 f.write(data) 167 f.close() 168 u.close() 169 return True 170 except urllib2.HTTPError,e: #HTTPError必须排在URLError的前面 171 logging.error("HTTPError code:" + str(e.code) + " - Content:" + e.read()) 172 return False 173 except urllib2.URLError, e: 174 logging.error("URLError reason:" + str(e.reason) + " - " + str(e)) 175 return False 176 except Exception, e: 177 logging.error(u"保存图片失败:" + str(e)) 178 return False 179 180 # 创建新目录 181 def createFolder(self, path): 182 path = path.strip() 183 # 判断路径是否存在 184 isExists=os.path.exists(path) 185 # 判断结果 186 if not isExists: 187 # 如果不存在则创建目录 188 logging.info(u"创建文件夹:'" + path + u"'") 189 # 创建目录操作函数 190 os.makedirs(path) 191 return True 192 else: 193 # 如果目录存在则不创建,并提示目录已存在 194 logging.info(u"名为'" + path + u"'的文件夹已经存在,跳过") 195 return False 196 197 # 获取的首页地址 198 def savePageInfo(self, pageURL): 199 logging.info(u"准备获取网页内容(超时设置:60秒):" + pageURL) 200 contents = self.getContents(pageURL, 60) 201 logging.info(u"网页内容获取完成,子路径个数:" + str(len(contents))) 202 index = 1 203 for item in contents: 204 #(1)item[0]子路径URL, item[1]子路径名称 205 folderURL = item[0] 206 folderName = self.contentFolder + '\' + str(index) + "-" + self.getPathName(item[1]) 207 self.createFolder(folderName) 208 index = index + 1 209 210 #(2)判断链接头部合法性和重复性 211 if not folderURL.startswith('http://'): 212 folderURL = pageURL + folderURL 213 if folderURL in self.urlDB: 214 logging.info(u'"' + folderName + u'"的链接地址(已访问,跳过)为:' + folderURL) 215 continue 216 else: 217 logging.info(u'"' + folderName + u'"的链接地址为:' + folderURL) 218 219 #(3)获取图片URL列表,成功则保存图片 220 images, succeed = self.getAllImgURL(folderURL) 221 if succeed: 222 succeed = self.saveImgs(images, folderName) 223 if succeed: 224 self.urlDB.add(folderURL) 225 self.writeToDatabase(folderURL) 226 227 # 初始化系统日志存储 228 InitLogger() 229 # 传入初始网页地址,自动启动爬取图片: 230 spider = Spider() 231 spider.loadDatabase() 232 spider.savePageInfo('http://365.tw6000.com/xtu/') 233 logging.info(u"全部网页内容爬取完成!程序退出。")
二、问题历史
在上面的代码中有不少的细节是优化解决过的,相关的知识点如下:
Python的日志系统是相当的不错,非常的方便,详细的资料可以参考Python官方文档,或者上一篇博文也是提到过的:《Python中的日志管理Logging模块》,应用到我这个爬虫这里的代码就是myLogger.py模块了,用起来很方便:
1 __author__ = 'KLH' 2 # -*- coding:utf-8 -*- 3 4 import logging 5 import time 6 7 def InitLogger(): 8 logFileName = 'log_' + time.strftime("%Y%m%d%H%M%S", time.localtime(time.time())) + '.txt' 9 logging.basicConfig(level=logging.DEBUG, 10 format='[%(asctime)s][%(filename)s:%(lineno)d][%(levelname)s] - %(message)s', 11 filename=logFileName, 12 filemode='w') 13 14 # 定义一个StreamHandler将INFO级别以上的信息打印到控制台 15 console = logging.StreamHandler() 16 console.setLevel(logging.INFO) 17 formatter = logging.Formatter('[%(asctime)s][%(filename)s:%(lineno)d][%(levelname)s] - %(message)s') 18 console.setFormatter(formatter) 19 logging.getLogger('').addHandler(console)