• 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 解析器更加强大,速度更快,推荐安装。

  • 相关阅读:
    HDU 4611 Balls Rearrangement 数学
    Educational Codeforces Round 11 D. Number of Parallelograms 暴力
    Knockout.Js官网学习(简介)
    Entity Framework 关系约束配置
    Entity Framework Fluent API
    Entity Framework DataAnnotations
    Entity Framework 系统约定配置
    Entity Framework 自动生成CodeFirst代码
    Entity Framework CodeFirst数据迁移
    Entity Framework CodeFirst尝试
  • 原文地址:https://www.cnblogs.com/kuliuheng/p/4951740.html
Copyright © 2020-2023  润新知