Python妹子图网站爬取

Python爬虫页面分析

我们今天要爬的网站叫做 http://www.meizitu.com/a/pure.html

为啥爬取这个网站,因为好爬。

好了,接下来分析这个页面
Python妹子图网站爬取_第1张图片
做爬虫很重要的一点,就是你要找到分页的地方,因为有分页代表着有规律,有规律,我们就好爬了(可以做的更智能一些,输入首页网址,爬虫自己就能分析到这个网站中的所有地址)

上面图片中,我们发现了分页,那么找规律吧
Python妹子图网站爬取_第2张图片
使用火狐浏览器的开发者工具,发现分页规律

http://www.meizitu.com/a/pure_1.html
http://www.meizitu.com/a/pure_2.html
http://www.meizitu.com/a/pure_3.html
http://www.meizitu.com/a/pure_4.html

好了,接下来用Python实现这部分(以下写法有部分面向对象的写法,没有基础的同学,请百度找些基础来看,不过对于想学习的你来说,这些简单极了)

import requests
all_urls = []  #我们拼接好的图片集和列表路径
class Spider():
    #构造函数,初始化数据使用
    def __init__(self,target_url,headers):
        self.target_url = target_url
        self.headers = headers

    #获取所有的想要抓取的URL
    def getUrls(self,start_page,page_num):
        
        global all_urls
        #循环得到URL
        for i in range(start_page,page_num+1):
            url = self.target_url  % i
            all_urls.append(url)

if __name__ == "__main__":
    headers = {
            'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0',
            'HOST':'www.meizitu.com'
    }
    target_url = 'http://www.meizitu.com/a/pure_%d.html' #图片集和列表规则
    
    spider = Spider(target_url,headers)
    spider.getUrls(1,16)
    print(all_urls)

上面的代码,可能需要有一定的Python基础可以看懂,不过你其实仔细看一下,就几个要点

第一个是 class Spider(): 我们声明了一个类,然后我们使用 def __init__去声明一个构造函数,这些我觉得你找个教程30分钟也就学会了。

拼接URL,我们可以用很多办法,我这里用的是最直接的,字符串拼接。

注意上述代码中有一个全局的变量 all_urls 我用它来存储我们的所有分页的URL

接下来,是爬虫最核心的部分代码了

我们需要分析页面中的逻辑。首先打开 http://www.meizitu.com/a/pure_1.html ,右键审查元素。
Python妹子图网站爬取_第3张图片
Python妹子图网站爬取_第4张图片
发现上图红色框框里面的链接

点击图片之后,发现进入一个图片详情页面,发现竟然是一组图片,那么现在的问题是

我们要解决第一步,需要在 http://www.meizitu.com/a/pure_1.html 这种页面中爬取所有的 http://www.meizitu.com/a/5585.html 这种地址

这里我们采用多线程的方式爬取(这里还用了一种设计模式,叫观察者模式)

import threading   #多线程模块
import re #正则表达式模块
import time #时间模块

首先引入三个模块,分别是多线程,正则表达式,时间模块

新增加一个全局的变量,并且由于是多线程操作,我们需要引入线程锁

all_img_urls = []       #图片列表页面的数组

g_lock = threading.Lock()  #初始化一个锁  

声明一个生产者的类,用来不断的获取图片详情页地址,然后添加到 all_img_urls 这个全局变量中

#生产者,负责从每个页面提取图片列表链接
class Producer(threading.Thread):   

    def run(self):
        headers = {
            'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0',
            'HOST':'www.meizitu.com'
        }
        global all_urls
        while len(all_urls) > 0 :
            g_lock.acquire()  #在访问all_urls的时候,需要使用锁机制
            page_url = all_urls.pop()   #通过pop方法移除最后一个元素,并且返回该值
            
            g_lock.release() #使用完成之后及时把锁给释放,方便其他线程使用
            try:
                print("分析"+page_url)   
                response = requests.get(page_url , headers = headers,timeout=3)
                all_pic_link = re.findall('',response.text,re.S)   
                global all_img_urls
                g_lock.acquire()   #这里还有一个锁
                all_img_urls += all_pic_link   #这个地方注意数组的拼接,没有用append直接用的+=也算是python的一个新语法吧
                print(all_img_urls)
                g_lock.release()   #释放锁
                time.sleep(0.5)
            except:
                pass

上述代码用到了继承的概念,我从threading.Thread中继承了一个子类,继承的基础学习,你可以去翻翻 http://www.runoob.com/python3/python3-class.html 菜鸟教程就行。

线程锁,在上面的代码中,当我们操作all_urls.pop()的时候,我们是不希望其他线程对他进行同时操作的,否则会出现意外,所以我们使用g_lock.acquire()锁定资源,然后使用完成之后,记住一定要立马释放g_lock.release(),否则这个资源就一直被占用着,程序无法进行下去了。

匹配网页中的URL,我使用的是正则表达式,后面我们会使用其他的办法,进行匹配。

re.findall()方法是获取所有匹配到的内容,正则表达式,你可以找一个30分钟入门的教程,看看就行。

代码容易出错的地方,我放到了

try: except: 里面,当然,你也可以自定义错误。

如果上面的代码,都没有问题,那么我们就可以在程序入口的地方编写

for x in range(2):
    t = Producer()
    t.start()

执行程序,因为我们的Producer继承自threading.Thread类,所以,你必须要实现的一个方法是 def run 这个我相信在上面的代码中,你已经看到了。然后我们可以执行啦~~~

运行结果:
Python妹子图网站爬取_第5张图片
这样,图片详情页面的列表就已经被我们存储起来了。

接下来,我们需要执行这样一步操作,我想要等待图片详情页面全部获取完毕,在进行接下来的分析操作。

这里增加代码

#threads= []   
#开启两个线程去访问
for x in range(2):
    t = Producer()
    t.start()
    #threads.append(t)

# for tt in threads:
#     tt.join()

print("进行到我这里了")

注释关键代码,运行如下

[linuxboy@bogon demo]$ python3 down.py
分析http://www.meizitu.com/a/pure_2.html
分析http://www.meizitu.com/a/pure_1.html
进行到我这里了
['http://www.meizitu.com/a/5585.html', 

把上面的tt.join等代码注释打开

[linuxboy@bogon demo]$ python3 down.py
分析http://www.meizitu.com/a/pure_2.html
分析http://www.meizitu.com/a/pure_1.html
['http://www.meizitu.com/a/5429.html', ......

发现一个本质的区别,就是,我们由于是多线程的程序,所以,当程序跑起来之后,print(“进行到我这里了”)不会等到其他线程结束,就会运行到,但是当我们改造成上面的代码之后,也就是加入了关键的代码 tt.join() 那么主线程的代码会等到所以子线程运行完毕之后,在接着向下运行。这就满足了,我刚才说的,先获取到所有的图片详情页面的集合,这一条件了。

join所完成的工作就是线程同步,即主线程遇到join之后进入阻塞状态,一直等待其他的子线程执行结束之后,主线程在继续执行。这个大家在以后可能经常会碰到。

下面编写一个消费者/观察者,也就是不断关注刚才我们获取的那些图片详情页面的数组。

添加一个全局变量,用来存储获取到的图片链接

pic_links = []            #图片地址列表
#消费者
class Consumer(threading.Thread) : 
    def run(self):
        headers = {
            'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0',
            'HOST':'www.meizitu.com'
        }
        global all_img_urls   #调用全局的图片详情页面的数组
        print("%s is running " % threading.current_thread)
        while len(all_img_urls) >0 : 
            g_lock.acquire()
            img_url = all_img_urls.pop()
            g_lock.release()
            try:
                response = requests.get(img_url , headers = headers )
                response.encoding='gb2312'   #由于我们调用的页面编码是GB2312,所以需要设置一下编码
                title = re.search('(.*?) | 妹子图',response.text).group(1)
                all_pic_src = re.findall('.*?src="(.*?)"
',response.text,re.S) pic_dict = {title:all_pic_src} #python字典 global pic_links g_lock.acquire() pic_links.append(pic_dict) #字典数组 print(title+" 获取成功") g_lock.release() except: pass time.sleep(0.5)

看到没有,上面的代码其实和我们刚才写的极其相似,后面,我会在github上面把这部分代码修改的更加简洁一些,不过这才是第二课,后面我们的路长着呢。

代码中比较重要的一些部分,我已经使用注释写好了,大家可以直接参考。大家一定要注意我上面使用了两个正则表达式,分别用来匹配title和图片的url这个title是为了后面创建不同的文件夹使用的,所以大家注意吧。

#开启10个线程去获取链接
for x in range(10):
    ta = Consumer()
    ta.start()

运行结果:

[linuxboy@bogon demo]$ python3 down.py
分析http://www.meizitu.com/a/pure_2.html
分析http://www.meizitu.com/a/pure_1.html
['http://www.meizitu.com/a/5585.html', ......
 is running 
 is running 
 is running 
 is running 
 is running 
 is running 
 is running 
 is running 
进行到我这里了
 is running 
 is running 
清纯美如画,摄影师的御用麻豆 获取成功
宅男女神叶梓萱近日拍摄一组火爆写真 获取成功
美(bao)胸(ru)女王带来制服诱惑 获取成功
每天睁开眼看到美好的你,便是幸福 获取成功
可爱女孩,愿暖风呵护纯真和执着 获取成功
清纯妹子如一缕阳光温暖这个冬天 获取成功 .....

是不是感觉距离成功有进了一大步

接下来就是,我们开篇提到的那个存储图片的操作了,还是同样的步骤,写一个自定义的类

class DownPic(threading.Thread) :

    def run(self):
        headers = {
            'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0',
            'HOST':'mm.chinasareview.com'
        
        }
        while True:   #  这个地方写成死循环,为的是不断监控图片链接数组是否更新
            global pic_links
            # 上锁
            g_lock.acquire()
            if len(pic_links) == 0:   #如果没有图片了,就解锁
                # 不管什么情况,都要释放锁
                g_lock.release()
                continue
            else:
                pic = pic_links.pop()
                g_lock.release()
                # 遍历字典列表
                for key,values in  pic.items():
                    path=key.rstrip("\\")
                    is_exists=os.path.exists(path)
                    # 判断结果
                    if not is_exists:
                        # 如果不存在则创建目录
                        # 创建目录操作函数
                        os.makedirs(path) 
                
                        print (path+'目录创建成功')
                        
                    else:
                        # 如果目录存在则不创建,并提示目录已存在
                        print(path+' 目录已存在') 
                    for pic in values :
                        filename = path+"/"+pic.split('/')[-1]
                        if os.path.exists(filename):
                            continue
                        else:
                            response = requests.get(pic,headers=headers)
                            with open(filename,'wb') as f :
                                f.write(response.content)
                                f.close

我们获取图片链接之后,就需要下载了,我上面的代码是首先创建了一个之前获取到title的文件目录,然后在目录里面通过下面的代码,去创建一个文件。

涉及到文件操作,引入一个新的模块

import os  #目录操作模块
# 遍历字典列表
for key,values in  pic.items():
    path=key.rstrip("\\")
    is_exists=os.path.exists(path)
    # 判断结果
    if not is_exists:
        # 如果不存在则创建目录
        # 创建目录操作函数
        os.makedirs(path) 

        print (path+'目录创建成功')
        
    else:
        # 如果目录存在则不创建,并提示目录已存在
        print(path+' 目录已存在') 
    for pic in values :
        filename = path+"/"+pic.split('/')[-1]
        if os.path.exists(filename):
            continue
        else:
            response = requests.get(pic,headers=headers)
            with open(filename,'wb') as f :
                f.write(response.content)
                f.close

Python妹子图网站爬取_第6张图片
Python妹子图网站爬取_第7张图片

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