zoukankan      html  css  js  c++  java
  • Python爬虫(图片)编写过程中遇到的问题

      最近我突然对网络爬虫开窍了,真正做起来的时候发现并不算太难,都怪我以前有点懒,不过近两年编写了一些程序,手感积累了一些肯定也是因素,总之,还是惭愧了。好了,说正题,我把这两天做爬虫的过程中遇到的问题总结一下:

      需求:做一个爬虫,爬取一个网站上所有的图片(只爬大图,小图标就略过)

      思路:1、获取网站入口,这个入口网页上有很多图片集合入口,进入这些图片集合就能看到图片链接了,所以爬取的深度为2,比较简单;2、各个子图片集合内所包含的图片链接有两种形式:一种是绝对图片路径(直接下载即可),另一种的相对图片路径(需要自行拼接当前网页路径)。总之这些子图集合的表现形式简单,没有考虑更复杂的情况。3、在爬取的过程中保存已成功爬取的路径,防止每次爬取都执行重复的任务,当然,当每次启动时要首先加载该历史数据库,剩下的就是细节了。

      快速链接:

    2.1 日志系统

    2.2 记录访问历史

    2.3 异常处理

    2.4 网页自动编码判断

    2.5 远程主机重置链接(Errno 10054)

    2.6 使用BeautifulSoup来分析网页

    一、全部代码

      直接先来代码,再详细说优化的过程和内容吧:

      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"全部网页内容爬取完成!程序退出。")
    查看全部代码

    二、问题历史

      在上面的代码中有不少的细节是优化解决过的,相关的知识点如下:

    2.1 日志系统

      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)
    查看myLogger.py

      注意在爬虫的代码执行前调用一下该函数:

    from myLogger import *
    #
    初始化系统日志存储 InitLogger()

      日志调用和打印的结果如下:

    logging.info(u"准备获取网页内容(超时设置:60秒):" + pageURL)
    
    #打印结果如下:
    [2015-11-09 22:35:02,976][spider2.py:182][INFO] - 准备获取网页内容(超时设置:60秒):http://365.XXXXX.com/xtu/

    2.2 记录访问历史

      这里记录URL的访问历史是为了防止执行重复任务,在内存中保持一个Set就能满足需求,在磁盘上可以简单的保存成一个TXT文件,每个URL保存成一行即可。所以这里是逻辑顺序应该是在初始化时创建一个Set用来保存访问历史,任务执行之前从数据库中加载访问历史,如果是首次运行尚未创建数据库还需要进行一次创建操作。然后就好办了,每次完成一个URL的访问就保存一下访问记录。相关代码如下:

     1 # 类初始化
     2 def __init__(self):
     3     self.contentFolder = u"抓取内容"
     4     self.dbName = "url.db"              # 1、定义数据库文件名
     5     self.createFolder(self.contentFolder)   # 2、创建内容存储目录
     6     self.urlDB = set()                # 3、创建内存数据库
     7 
     8 # 获取URL数据库以获取爬过的网页地址
     9 def loadDatabase(self):
    10     isExists = os.path.exists(self.dbName)  # 4、首先判断是否是首次运行,如果数据库文件不存在则创建一下
    11     if not isExists:
    12         logging.info(u"创建URL数据库文件:'" + self.dbName + u"'")
    13         f = open(self.dbName, 'w+')
    14         f.write("#Create time: " + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) + '
    ')
    15         f.close()
    16         return
    17     db = open(self.dbName, 'r')         # 5、从磁盘中加载数据库
    18     for line in db.readlines():
    19         if not line.startswith('#'):
    20             self.urlDB.add(line.strip('
    '))
    21     db.close()
    22     logging.info(u"URL数据库加载完成!")
    23 
    24 # 追加数据库文件
    25 def writeToDatabase(self, url):         # 6、在系统运行过程中,如需记录日志,追加日志内容即可
    26     db = open(self.dbName, 'a')
    27     db.write(url + '
    ')
    28     db.close()

      有了上面的代码,在记录日志过程中就很方便了:

    succeed = self.saveImgs(images, folderName)
        if succeed:
            self.urlDB.add(folderURL)
            self.writeToDatabase(folderURL)

    2.3 异常处理

      访问网络资源不可避免的会有很多异常情况,要处理这些异常情况才能稳定运行,Python的异常处理很简单,请参考如下获取网页的代码段:

     1 # 获取索引页面的内容
     2 def getPage(self, pageURL, second):
     3     try:
     4         request = urllib2.Request(pageURL)
     5         response = urllib2.urlopen(request, timeout = second)
     6         data = response.read()
     7         return data.decode('gbk'), True
     8     except urllib2.HTTPError,e:    # HTTPError必须排在URLError的前面
     9         logging.error("HTTPError code:" + str(e.code) + " - Content:" + e.read())
    10         return "", False
    11     except urllib2.URLError, e:
    12         logging.error("URLError reason:" + str(e.reason) + " - " + str(e))
    13         return "", False
    14     except Exception, e:       # 其他所有类型的异常
    15         logging.error(u"获取网页失败:" + str(e))
    16         return "", False       # 这里返回两个值方便判断

    2.4 网页自动编码判断

      昨天在爬取网页的过程中突然发现,有的页面居然说编码不能通过utf-8进行解析,我看了看有的网页确实不是utf-8编码的,那怎么办呢?怎么才能自动进行解码?从网上可以搜索到一个Python的开源库很好用,叫做chardet,默认Python2.7是不带的需要下载,比如我下载的是:chardet-2.3.0.tar.gz

      有了这个压缩包解压出来cahrdet子文件夹拷贝到:C:Python27Libsite-packages目录下即可。下面看看用法实例:

     1 import chardet
     2 
     3 # 获取页面所有图片
     4 def getAllImgURL(self, infoURL):
     5   images = []
     6   succeed = True
     7   try:
     8     data = urllib2.urlopen(infoURL).read()  # 1、先获取网页内容
     9     chardet1 = chardet.detect(data)        # 2、再调用该模块的方法自动判断网页编码
    10     page = data.decode(str(chardet1['encoding']))   # 3、注意,得到的chardet1是一个字典类似于:{'confidence': 0.98999999999999999, 'encoding': 'GB2312'}
    11     
    12     # 第一种解码格式:
    13     pattern = re.compile('<option value="(.*?)">(.*?)</option>')
    14     items = re.findall(pattern, page)
    15     # item[0]为图片URL尾部,item[1]为图片名称
    16     for item in items:
    17       imageURL = infoURL + item[0]
    18       if imageURL in self.urlDB:
    19         logging.info(u"获得图片URL(曾被访问,跳过):" + imageURL)
    20       else:
    21         logging.info(u"获得图片URL:" + imageURL)
    22         images.append(imageURL) 
    23             
    24     # 第二种解码格式
    25     pattern = re.compile('<IMG src="(.*?)".*?>')
    26     items = re.findall(pattern, page)
    27     # item为图片URL
    28     for item in items:
    29       if item.startswith('http://'):    # 4、这里也注意一下,在这种网页中相对路径的图片都是插图之类的小图片不需要下载,所以过滤掉了。
    30         if item in self.urlDB:
    31             logging.info(u"获得图片URL(曾被访问,跳过):" + item)
    32         else:
    33           logging.info(u"获得图片URL:" + item)
    34           images.append(item)             
    35           
    36   except Exception, e:
    37     logging.warning(u"在获取子路径图片列表时出现异常:" + str(e))
    38     succeed = False
    39   return images, succeed

    2.5 远程主机重置链接(Errno 10054)

      在今天的爬取过程中我发现了一个问题,爬到后面的内容都出错了,错误信息参见如下:

    [2015-11-09 23:48:51,082][spider2.py:130][INFO] - 开始准备保存图片(超时设置:300秒):http://xz1.XXXX.com/st/st-06/images/009.jpg
    [2015-11-09 23:48:55,095][spider2.py:160][ERROR] - 保存图片失败:[Errno 10054] 
    [2015-11-09 23:48:55,096][spider2.py:140][WARNING] - 保存图片失败(耗时:4.01399993896秒):http://xz1.XXXX.com/st/st-06/images/009.jpg
    [2015-11-09 23:48:55,098][spider2.py:130][INFO] - 开始准备保存图片(超时设置:300秒):http://xz1.XXXX.com/st/st-06/images/010.jpg
    [2015-11-09 23:48:56,576][spider2.py:160][ERROR] - 保存图片失败:[Errno 10054] 
    [2015-11-09 23:48:56,578][spider2.py:140][WARNING] - 保存图片失败(耗时:1.48000001907秒):http://xz1.XXXX.com/st/st-06/images/010.jpg

      可以看到都在报这个错误代码,网上一查,发现有很多同学已经遇到过了,请参考知乎上的讨论:http://www.zhihu.com/question/27248551

      从网上讨论的结果来看,可以肯定的是直接原因在于“远程主机主动关闭了当前链接”,而根本原因则在于:网站启用了反爬虫的策略,也就是说我的爬虫爬的过快并且被发现不是真正的浏览器浏览了。怎么办了?针对这两个原因逐个处理,一是伪装成浏览器,二是不要访问的过快,三是每次访问完成都关闭链接。相关代码如下:

     1 # 传入图片地址,文件名,超时时间,保存单张图片
     2 def saveImg(self, imageURL, fileName, second):
     3   try:            
     4     headers = {'User-agent' : 'Mozilla/5.0 (Windows NT 6.2; WOW64; rv:22.0) Gecko/20100101 Firefox/22.0'}  # 1、这里构造一个浏览器的头部
     5     request = urllib2.Request(imageURL, headers = headers)
     6     u = urllib2.urlopen(request, timeout = second)     # 2、这里的超时设置timeout不要太长
     7     data = u.read()
     8     f = open(fileName, 'wb')
     9     f.write(data)
    10     f.close()
    11     u.close()    # 3、注意这里的链接要主动调用一下关闭
    12     return True
    13   except urllib2.HTTPError,e:    #HTTPError必须排在URLError的前面
    14     logging.error("HTTPError code:" + str(e.code) + " - Content:" + e.read())
    15     return False
    16   except urllib2.URLError, e:
    17     logging.error("URLError reason:" + str(e.reason) + " - " + str(e))
    18     return False
    19   except Exception, e:
    20     logging.error(u"保存图片失败:" + str(e))
    21     return False  # 4、注意调用完这个函数之后再time.sleep(1)一下,防止过快访问被发现了,呵呵

     2.6 使用BeautifulSoup来分析网页

       使用正则表达式来匹配网页中的字段确实是太费劲了。用BeautifulSoup就省力多了,功能很强大:

    soup = BeautifulSoup(page_data, 'lxml')
    entries = soup.find_all(src=re.compile('.jpg'), border='0')    # 找到所有字段为src正则匹配的标签内容,同时增加一个约束条件是border=‘0’

       Beautiful Soup支持Python标准库中的HTML解析器,还支持一些第三方的解析器,如果我们不安装它,则 Python 会使用 Python默认的解析器,lxml 解析器更加强大,速度更快,推荐安装。

  • 相关阅读:
    C#托盘图标
    线程相关整理
    Quartz.NET 快速入门
    (转)IE内存泄露,iframe内存泄露造成的原因和解决方案
    美化console.log的文本(转载)
    mongoDB学习资料整理
    EF7学习资料整理
    Oracle常用
    Node.js学习资料整理
    【大型网站技术实践】初级篇:借助Nginx搭建反向代理服务器(转)
  • 原文地址:https://www.cnblogs.com/kuliuheng/p/4951740.html
Copyright © 2011-2022 走看看