python3爬虫学习

Python3爬虫学习--多线程爬取图片

python3爬虫


本人是个python爬虫小白,也没有任何编程经验,在一个偶然的机会浏览某论坛发现了大量的妹子图,于是想着能不能写个简单的爬虫将这些图片爬下来再看,于是在网上找各种资料发现python实现爬虫比较简单、入门也低,于是自己慢慢摸索写了个论坛妹子图片的多线程爬虫,实现将特定主题帖子中的妹子图爬取后保存在本地PC中。

python3环境准备

  • 先到www.python.org官网下载python安装包,建议新手学习使用python3吧,本人使用的是python-3.5.4-amd64安装包,安装完成后就可以使用python shell环境写代码了,但是这种环境不适合软件工程开发。

  • 建议使用sublime text或者pycharm集成开发环境,sublime text适合轻量级软件工程开发,而pycharm适合大型软件工程开发,具体安装配置使用请自行百度下。

python3第三方库安装

  • python3本身也内置了urllib网页请求模块,但是我比较喜欢用requests这个库,因为简单易懂。

    • 在windows cmd命令窗口中安装requests库:pip3 install requests

  • 还有个就是网页解析库,网上多数人建议使用bs4库,但是我发现lxml库的效率比bs4快些,我个人比较倾向于使用lxml库。

    • 在windows cmd命令窗口中安装lxml库:pip3 install lxml

最后用pip3 list确认下是否安装成功,如果cmd窗口中显示如下则表示安装成功了。

爬虫准备工作--分析网站结构

国内著名的MM图片论坛网站,url就不放出来了(容易被和谐,但我的GitHub源码中有),一般的论坛网站都是首页->论坛主题->帖子主题列表->帖子主题内容,这样的页面结构。可以根据这种页面结构来组织编写代码,代码实现逻辑如下图所示:

爬虫代码实现说明

  • get_html代码说明

  
  def get_html(url):
      try:
          # 使用requests库中的get方法请求页面
          r = s.get(url=url,headers=header,timeout=12)
          if r.status_code == 404:
              print(' %s 响应状态码非200 OK,正在重试中......' % url)
              time.sleep(6)
              r = s.get(url=url,headers=header,timeout=15)
          global num
      except Exception as err:
          print(err,'\n请求 %s 失败,正在重试中......' % url)
          time.sleep(3)
          while True:
              try:
                  r = s.get(url=url,headers=header,timeout=15)
                  if r.status_code == 404:
                      print(' %s 响应状态码非200 OK,正在重试中......' % url)
                      time.sleep(6)
                      r = s.get(url=url,headers=header,timeout=15)
              except Exception as err:
                  print(err,'\n请求重试 %s 继续失败,继续重试中......' % url)
                  time.sleep(3)
              else:
                  r.encoding = 'GB18030'
                  if r.text.find('404 Not Found') != -1:
                      print(' %s 重试失败!放弃访问!' % url,r)
                      return r.text
                  else:
                      print('请求重试 %s 成功!' % ('【'+str(num)+'】'+ url),r)
                      num += 1
                      return r.text
      else:
          r.encoding = 'GB18030'
          if r.text.find('404 Not Found') != -1:
              print(' %s 重试失败!放弃访问!' % url,r)
              return r.text
          else:
              print(' %s 访问成功!' % ('【'+str(num)+'】'+ url),r)
              num += 1
              return r.text

其中最外层try中的代码块实现页面请求以及如果响应状态码非200ok时会重新请求一次;最外层的except中代码块捕捉到任何请求异常就会重新发起页面请求直至成功;最外层else中代码块作用为当try语句正常执行后就会执行else中的语句。

  • list_page代码说明

  
  def list_page(url):
      page = get_html(url)
      # 使用lxml库的html模块解析页面
      tree = html.fromstring(page)
      # 先使用lxml的xpath方法获取所有帖子主题列表的标签,然后利用正则表达式过滤所需要的所有帖子主题列表url
      pages_url = re.findall(r'htm_data/16/17\d+/\d{7}\.html',str(tree.xpath('//h3/a/@href')))
      for i in range(len(pages_url)):
      # 拼接真正的帖子主题列表url地址
          pages_url[i] = 'http://cl.***.xyz/'+pages_url[i]
      return pages_url

此函数的功能是获取每页中所有主题列表的url地址。

  • get_page_html代码说明

  def get_page_html(url):
      pages = list_page(url)
          for page in pages:
              page = get_html(page)
              tree = html.fromstring(page)
              # 使用lxml的xpath方法获取主题内容的标题
              title_name = tree.xpath('//h4/text()')
              if len(title_name) != 0:
                  # 替换不符合windows系统文件命名的特殊符号
                  file_name = title_name[0].replace(':','_')\
                                          .replace('?','_')\
                                          .replace('<','(')\
                                          .replace('>',')')\
                                          .replace('"',' ')\
                                          .replace('*','_')\
                                          .replace('\u301c','')\
                                          .replace('\xa0','_')
              else:
                  file_name = None
                  continue
             # 使用lxml的xpath方法获取主题内容中图片的url地址 
              img_url_list = tree.xpath('//input/@src')
              # 判断主题文件夹是否存在,如果没有则直接创建
              if not os.path.exists('D:\\Download\\Pictrue\\'+file_name):
                  try:
                      os.makedirs('D:\\Download\\Pictrue\\' + file_name)
                      os.chdir('D:\\Download\\Pictrue\\' + file_name)
                      print('D:\Download\Pictrue\%s 主题目录创建成功!' % file_name)
                  except Exception as err:
                      print('主题目录创建失败!','\n'+err)
                      continue
              else:
                  print('D:\Download\Pictrue\%s 主题目录已存在!' % file_name)
              for img_url in img_url_list:
                  # 图片文件名和替换不符合系统文件命名规则的特殊符号
                  img_name = img_url.split('/')[-1].translate(str.maketrans('<>?=&','____.'))
                 # 判断图片是否下载过,如果没有就直接下载 
                  if not os.path.isfile('D:\\Download\\Pictrue\\' + file_name+ '\\' + img_name):
                     # 调用多线程下载图片 
                      thread = download_thread(file_name,img_url,img_name)
                      thread.start()
                      time.sleep(1.5)
                  else:
                      print(img_name+' >>> 该图片已经下载过了!')
              print('该主题共有 %s 张图片!' % len(img_url_list))
              time.sleep(5) 

此函数的功能是获取每个帖子主题内容中的标题及图片的url地址并调用多线程下载图片

  • save_img代码说明

  
  def save_img(file_name,img_url,img_name):
      with open('D:\\Download\\Pictrue\\' + file_name + '\\' + img_name,'wb') as file:
          print('正在使用线程 %s 下载:' % threading.current_thread().name + file_name +' >>> '+ img_name)
          try:
              file.write(s.get(img_url,headers=header,timeout=15).content)
              print('线程 %s 下载完成!' % threading.current_thread().name)
          except Exception as err:
              print(err,'\n'+img_url+' >>> URL请求失败,正在重新下载......')
              time.sleep(3)
              # 下载失败会重试一次
              try:
                  file.write(s.get(img_url,headers=header,timeout=18).content)
                  print('线程 %s 重新下载完成!' % threading.current_thread().name)
              except Exception as err:
                  print(err,'\n'+img_url+' >>> URL请求继续失败,放弃下载......')

此函数功能是执行图片下载并保存在本地目录。

  • 入口函数的代码说明

  if __name__ == '__main__':
      ''' 1.爬取过于频繁会导致ip访问受限制,之后访问需要输入验证码
          2.根据不同网络环境判断访问是否需要输入验证码
          3.验证码保存在代码所在目录,并需要手动输入
      '''
      global r
      r = s.get('http://cl.***.xyz/index.php',headers=header,timeout=12)
      r.encoding = 'GB18030'
      #print(r,'\n'+r.text)
      if r.text.find('url=codeform.php') != -1:
          with open('code.png', 'wb') as file:
              print('正在下载验证码图片......\n请到 %s 目录找到code.png并手动输入!' % os.path.abspath('code.png'))
              file.write(s.get('http://cl.***.xyz/require/codeimg.php',headers=header,timeout=15).content)
              file.close()
          code = input('请输入验证码:')
          postdata['validate'] = code
          r = s.post('http://cl.***.xyz/codeform.php',headers=header,data=postdata)
          r.encoding = 'GB18030'
          while r.text.find('输入有误') != -1:
              print('验证码输入有误!')
              with open('code.png', 'wb') as file:
                  file.write(s.get('http://cl.***.xyz/require/codeimg.php',headers=header,timeout=15).content)
                  file.close()
              code = input('请重新输入验证码:')
              postdata['validate'] = code
              r = s.post('http://cl.***.xyz/codeform.php',headers=header,data=postdata)
              r.encoding = 'GB18030'
          for index in range(100):
              index += 1
              url = 'http://cl.***.xyz/thread0806.php?fid=16&search=&page='+str(index)
              get_page_html(url)
              time.sleep(3)
      else:
          for index in range(100):
              index += 1
              url = 'http://cl.***.xyz/thread0806.php?fid=16&search=&page='+str(index)
              get_page_html(url)
              time.sleep(3)

代码入口函数实现验证码登入(实际上如果爬取不是很频繁是不需要输入验证码的)及爬取前100页的图片资源。

完整GitHub源码链接

由于刚学习python,很多技巧还没有掌握,文章中的代码还有很多处是可以优化的,比如第一个函数和最后一个函数重复的代码比较多,希望高手能够指点下如何优化,大家互相学习嘛!

你可能感兴趣的:(图片爬虫)