zoukankan      html  css  js  c++  java
  • Python爬虫入门教程 5-100 27270图片爬取

    27270图片----获取待爬取页面

    今天继续爬取一个网站,http://www.27270.com/ent/meinvtupian/ 这个网站具备反爬,so我们下载的代码有些地方处理的也不是很到位,大家重点学习思路,有啥建议可以在评论的地方跟我说说。

    为了以后的网络请求操作方向,我们这次简单的进行一些代码的封装操作。

    在这里你可以先去安装一个叫做 retrying 的模块

    pip install retrying
    

    这个模块的具体使用,自己去百度吧。嘿嘿哒~

    在这里我使用了一个随机产生user_agent的方法

    
    import requests
    from retrying import retry
    import random
    import datetime
    
    class R:
    
        def __init__(self,method="get",params=None,headers=None,cookies=None):
    		# do something
    
    
        def get_headers(self):
            user_agent_list = [ 
                "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1" 
                "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11", 
                "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6", 
                "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6", 
                "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1", 
                "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5", 
                "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5", 
                "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", 
                "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", 
                "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", 
                "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", 
                "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", 
                "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", 
                "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", 
                "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", 
                "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3", 
                "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24", 
                "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"
            ]
            UserAgent = random.choice(user_agent_list)
            headers = {'User-Agent': UserAgent}
            return headers
    	#other code
    
    
    

    retrying 最简单的使用就是给你想不断重试的方法加上 装饰器 @retry

    在这里,我希望网络请求模块尝试3次之后,在报错!

    同时在R类初始化方法中增加一些必备的参数,你可以直接看下面的代码

    __retrying_requests 方法为私有方法,其中根据getpost方式进行逻辑判断

    
    import requests
    from retrying import retry
    import random
    import datetime
    
    class R:
    
        def __init__(self,method="get",params=None,headers=None,cookies=None):
    		#do something
    
        def get_headers(self):
            # do something
        @retry(stop_max_attempt_number=3)
        def __retrying_requests(self,url):
            if self.__method == "get":
                response = requests.get(url,headers=self.__headers,cookies=self.__cookies,timeout=3)
            else:
                response = requests.post(url,params=self.__params,headers=self.__headers,cookies=self.__cookies,timeout=3)
            return response.content
    
       
    	# other code
    
    

    网络请求的方法已经声明完毕,并且返回 response.content 数据流

    下面基于这个私有方法,增加一个获取网络文本的方法和一个获取网络文件的方法。同步完善类的初始化方法,在开发中发现,我们要爬取的网页编码是gb2312 所以还需要给某些方法增加一个编码参数

    import requests
    from retrying import retry
    import random
    import datetime
    
    class R:
    	# 类的初始化方法
        def __init__(self,method="get",params=None,headers=None,cookies=None):
            self.__method = method
            myheaders = self.get_headers()
            if headers is not None:
                myheaders.update(headers)
            self.__headers = myheaders
            self.__cookies = cookies
            self.__params = params
    
    
        def get_headers(self):
           # do something
    
        @retry(stop_max_attempt_number=3)
        def __retrying_requests(self,url):
    		# do something
    
        # get请求
        def get_content(self,url,charset="utf-8"):
            try:
                html_str = self.__retrying_requests(url).decode(charset)
            except:
                html_str = None
            return html_str
    
        def get_file(self,file_url):
            try:
                file = self.__retrying_requests(file_url)
            except:
                file = None
            return file
    
    

    到此,这个R类已经被我们完善了,完整的代码,你应该从上面拼凑起来,你也可以直接翻到文章最后面,去github上直接查阅。

    接下来,就是比较重要的爬虫代码部分了。这一次,我们可以简单的使用一下类和对象,并且加上简单的多线程操作。

    首先,创建一个 ImageList 类,这个类第一件事情,需要获取我们爬取页面的总页码数目

    在这里插入图片描述

    这个步骤比较简单

    1. 获取网页源码
    2. 正则匹配末页元素
    3. 提取数字
    import http_help as hh   # 这个http_help 是我上面写到的那个R类
    import re
    import threading
    import time
    import os
    import requests
    
    # 获取所有待爬取的URL列表
    class ImageList():
        def __init__(self):
            self.__start = "http://www.27270.com/ent/meinvtupian/list_11_{}.html"  # URL模板
            # 头文件
            self.__headers = {"Referer":"http://www.27270.com/ent/meinvtupian/",
                              "Host":"www.27270.com"
                              }
            self.__res = hh.R(headers=self.__headers)  # 初始化访问请求
        def run(self):
            page_count =  int(self.get_page_count())
    
            if page_count==0:
                return
            urls = [self.__start.format(i) for i in range(1,page_count)]
            return urls
    
    
    	# 正则表达式匹配末页,分析页码
        def get_page_count(self):
    		# 注意这个地方需要传入编码
            content = self.__res.get_content(self.__start.format("1"),"gb2312")
            pattern = re.compile("<li><a href='list_11_(d+?).html' target='_self'>末页</a></li>")
            search_text = pattern.search(content)
            if search_text is not None:
                count = search_text.group(1)
                return count
            else:
                return 0
    if __name__ == '__main__':
        img = ImageList()
        urls = img.run()
    

    上面的代码注意get_page_count方法,该方法已经获取到了末尾的页码

    在这里插入图片描述

    我们在run方法内部,通过一个列表生成器

    urls = [self.__start.format(i) for i in range(1,page_count)]
    

    批量把要爬取的所有链接都生成完毕。

    27270图片----分析上面爬取到的URL列表,捕获详情页

    我们采用生产者和消费者模型,就是一个抓取链接图片,一个下载图片,采用多线程的方式进行操作,需要首先引入

    import threading
    import time
    

    完整代码如下

    import http_help as hh
    import re
    import threading
    import time
    import os
    import requests
    
    urls_lock = threading.Lock()  #url操作锁
    imgs_lock = threading.Lock()  #图片操作锁
    
    imgs_start_urls = []
    
    
    class Product(threading.Thread):
        # 类的初始化方法
        def __init__(self,urls):
            threading.Thread.__init__(self)
            self.__urls = urls
            self.__headers = {"Referer":"http://www.27270.com/ent/meinvtupian/",
                              "Host":"www.27270.com"
                              }
    
            self.__res = hh.R(headers=self.__headers)
    
    	# 链接抓取失败之后重新加入urls列表中
        def add_fail_url(self,url):
            print("{}该URL抓取失败".format(url))
            global urls_lock
            if urls_lock.acquire():
                self.__urls.insert(0, url)
                urls_lock.release()  # 解锁
    	
    	# 线程主要方法
        def run(self):
            print("*"*100)
            while True:
                global urls_lock,imgs_start_urls
                if len(self.__urls)>0:
                    if urls_lock.acquire():   # 锁定
                        last_url = self.__urls.pop()   # 获取urls里面最后一个url,并且删除
                        urls_lock.release()  # 解锁
    
                    print("正在操作{}".format(last_url))
    
                    content = self.__res.get_content(last_url,"gb2312")   # 页面注意编码是gb2312其他格式报错
                    if content is not  None:
                        html = self.get_page_list(content)
    
                        if len(html) == 0:
                            self.add_fail_url(last_url)
                        else:
                            if imgs_lock.acquire():
                                imgs_start_urls.extend(html)    # 爬取到图片之后,把他放在待下载的图片列表里面
                                imgs_lock.release()
    
                        time.sleep(5)
                    else:
                        self.add_fail_url(last_url)
    
                else:
                    print("所有链接已经运行完毕")
                    break
    
    
    
    
    
        def get_page_list(self,content):
    		# 正则表达式
            pattern = re.compile('<li> <a href="(.*?)" title="(.*?)" class="MMPic" target="_blank">.*?</li>')
            list_page = re.findall(pattern, content)
    
            return list_page
    
    
    

    上述代码中比较重要的有
    threading.Lock() 锁的使用,在多个线程之间操作全局变量,需要进行及时的锁定;
    其他的注意内容,我已经添加在注释里面,只要你按着步骤一点点的写,并且加入一些自己微妙的理解,就可以搞定。

    到现在为止,我们已经抓取到了所有的图片地址,我把他存放在了一个全局的变量里面 imgs_start_urls
    那么现在又来了

    这个列表里面存放的是 http://www.27270.com/ent/meinvtupian/2018/298392.html 这样的地址,当你打开这个页面之后,你会发现只有一张图片 ,并且下面有个分页。
    在这里插入图片描述
    在这里插入图片描述

    点击分页之后,就知道规律了

    http://www.27270.com/ent/meinvtupian/2018/298392.html 
    http://www.27270.com/ent/meinvtupian/2018/298392_2.html 
    http://www.27270.com/ent/meinvtupian/2018/298392_3.html 
    http://www.27270.com/ent/meinvtupian/2018/298392_4.html 
    ....
    

    当你进行多次尝试之后,你会发现,后面的链接完全可以靠拼接完成,如果没有这个页面,那么他会显示?

    在这里插入图片描述

    好了,如果你进行了上面的操作,你应该知道接下来怎么实现啦!

    我把所有的代码,都直接贴在下面,还是用注释的方式给大家把最重要的地方标注出来

    class Consumer(threading.Thread):
    	# 初始化
        def __init__(self):
            threading.Thread.__init__(self)
            self.__headers = {"Referer": "http://www.27270.com/ent/meinvtupian/",
                              "Host": "www.27270.com"}
            self.__res = hh.R(headers=self.__headers)
    
    	# 图片下载方法
        def download_img(self,filder,img_down_url,filename):
            file_path = "./downs/{}".format(filder)
            
    		# 判断目录是否存在,存在创建
            if not os.path.exists(file_path):
                os.mkdir(file_path)  # 创建目录
    
            if os.path.exists("./downs/{}/{}".format(filder,filename)):
                return
            else:
                try:
                    # 这个地方host设置是个坑,因为图片为了防止盗链,存放在另一个服务器上面
                    img = requests.get(img_down_url,headers={"Host":"t2.hddhhn.com"},timeout=3)
                except Exception as e:
                    print(e)
    
                print("{}写入图片".format(img_down_url))
                try:
                	# 图片写入不在赘述
                    with open("./downs/{}/{}".format(filder,filename),"wb+") as f:
                        f.write(img.content)
                except Exception as e:
                    print(e)
                    return
    
    
    
    
    
        def run(self):
    
            while True:
                global imgs_start_urls,imgs_lock
    
                if len(imgs_start_urls)>0:
                    if imgs_lock.acquire():  # 锁定
                        img_url = imgs_start_urls[0]   #获取到链接之后
                        del imgs_start_urls[0]  # 删掉第0项
                        imgs_lock.release()  # 解锁
                else:
                    continue
    
                # http://www.27270.com/ent/meinvtupian/2018/295631_1.html
    
                #print("图片开始下载")
                img_url = img_url[0]
                start_index = 1
                base_url = img_url[0:img_url.rindex(".")]    # 字符串可以当成列表进行切片操作
    
                while True:
    
                    img_url ="{}_{}.html".format(base_url,start_index)   # url拼接
                    content = self.__res.get_content(img_url,charset="gbk")   # 这个地方获取内容,采用了gbk编码
                    if content is not None:
                        pattern = re.compile('<div class="articleV4Body" id="picBody">[sS.]*?img alt="(.*?)".*? src="(.*?)" />')
    					# 匹配图片,匹配不到就代表本次操作已经完毕
                        img_down_url = pattern.search(content)  # 获取到了图片地址
    
                        if img_down_url is not None:
                            filder = img_down_url.group(1)
                            img_down_url = img_down_url.group(2)
                            filename = img_down_url[img_down_url.rindex("/")+1:]
                            self.download_img(filder,img_down_url,filename)  #下载图片
    
                        else:
                            print("-"*100)
                            print(content)
                            break # 终止循环体
    
                    else:
                        print("{}链接加载失败".format(img_url))
    
                        if imgs_lock.acquire():  # 锁定
                            imgs_start_urls.append(img_url)
                            imgs_lock.release()  # 解锁
    
                    start_index+=1   # 上文描述中,这个地方需要不断进行+1操作
                    
    

    所有的代码都在上面了,关键的地方我尽量加上了标注,你可以细细的看一下,实在看不明白,就多敲几遍,因为没有特别复杂的地方,好多都是逻辑。

    最后附上main部分的代码,让我们的代码跑起来

    
    if __name__ == '__main__':
    
        img = ImageList()
        urls = img.run()
        for i in range(1,2):
            p = Product(urls)
            p.start()
    
        for i in range(1,2):
            c = Consumer()
            c.start()
    
    

    一会过后,就慢慢收图吧
    在这里插入图片描述

    github地址

    这个版本的代码有点问题,对反爬处理的并不是很到位,大家先学习基础的,这个地方找时间,我在完善一下。

    隐藏彩蛋,重要的事情说100遍:爬虫入门,爬虫入门,爬虫入门,爬虫入门,爬虫入门,爬虫入门,爬虫入门,爬虫入门爬虫入门,爬虫入门,爬虫入门,爬虫入门,爬虫入门,爬虫入门,爬虫入门,爬虫入门

  • 相关阅读:
    并发队列、线程池、锁
    JVM、垃圾收集器
    Socket网络编程
    Netty入门
    SpringCloud微服务负载均衡与网关
    Android监听耳机按键事件
    利用本地不同磁盘文件夹作为git远程仓库进行灾备
    chrome浏览器form中button每点击一次,form就会提交一次
    [企业路由器] 一对一NAT映射设置指导
    win7镜像自带IE9的卸载
  • 原文地址:https://www.cnblogs.com/happymeng/p/10131952.html
Copyright © 2011-2022 走看看