Python实现爬取贴吧图片

导读:

       最近周边朋友学python的越来越多,毫无意外的是,大家都选择了爬虫入门。这不难理解。Python有丰富的库使用,使得爬虫的实现容易很多,学习之后,回报明显,容易获得成就感。总结起来就是:让人有继续学下去的欲望。我偏巧例外,先走了Python web。虽然起了个大早,赶了个晚集,但不妨趁清明假期,计划之外,时间有余,做一回“愿闻其详”的门外汉。

       探一探爬虫的入门知识,这里将通过爬取某贴吧的图片为案例介绍一下简单的爬虫需要掌握哪些知识,以及实现的流程。因本人能力有限,若有纰漏处,望各友予以指正,必改。

实验前准备:

1. 知识点:

          urllib: urlencode()

          urllib2: HTTPHandler(), ProxyHandler(), build_opener(), Request()

          lxml: etree, xpath

2. 推荐工具:

          Fiddler4

          xpath helper

3. 实现环境:

          Ubuntu, python2.7


正文:

1. 什么是爬虫

        事实上网上有很多对爬虫高深的解释,让人看了云里雾里,我觉得对于初学者,可以一言概之:获取服务器的响应数据,并从中分离出自己需要的部分。

        当然,不得不说这样解释很不严谨。可学一门新知识的时候,最重要的是,知道自己在学什么,至少心中对其有个大致的形象,而不是依稀的模糊的知识。所以入门可以尽可能的简单理解,往后需要深究的时候再回头看,必有大悟。

2. 如何获取服务器响应数据

        既然爬虫需要获取服务器的响应内容,那么第二步就是如何获取服务器数据。

        事实上,当我们用浏览器打开百度的时候,浏览器就已经在你不知道的情况下,先你的目标服务器(也就是百度的服务器)发送了请求报文Request Header),如图:

        

        服务器通过对请求报文的解析,相应的,会返还你一个响应报文Response Headers),如图:

        

         同时会在响应报文中,附带整个网页的代码,如图:

         

         

         详情可以查阅相关HTTP协议。

         所以我们想要获得服务器响应的数据,就得尽可能使自己的代码运行时,像是寻常的浏览器。而服务器判别的标准,就是从请求报文里开始的(并非唯一方法)。

         python的库很强大,所以实现请求报文的构造,发送,同时获得响应内容很简单:

import urllib2

# 目的,也就是网址
url = "https://www.baidu.com/"
# 构建一个http对象
http = urllib2.HTTPHandler()
# 构建一个请求的发送器
opener = urllib2.build_opener(http) 
# 构造一个请求报文
header = {
"User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36"
}
request = urllib2.Request(url, headers = header)
# 发送请求报文,并且返回响应对象 
response = opener.open(request)
print response.read()
# 打印对象的内容
            (注:header 是个一个字典)

3. 对url的分析

        首先,我们需要对贴吧“翻页”理解,贴吧是如何实现翻页的呢?以李毅吧为例:

        

        可以观察到的是前面“https://tieba.baidu.com/f?”没有发生变化。(当然kw="..."和ie="..."也没有改变,不急,马上道来)

          我们再试试其他贴吧,如美女吧:

          

          有兴趣可以多试试其他的贴吧,发现kw值不一样。事实上url中“?”(问号)之后的内容其实就是浏览器向服务器请求数据的参数,即get,还有一种是post。二者的区别是:get方式传递参数会显示在url当中,而post方式参数放在Form表单中。举个例子,在人人的登陆页面,如果键入错误的信息,url不会发生改变,浏览器像是什么事儿都没做一样,事实上,已经通过Form表单传递我们键入的密码以及账号,并且由服务器验证,是否允许登陆(即:账号密码正确)。这里,我们可以通过Fiddler 4 抓包予以验证

            

                      

               

        其他的数据可以暂时不管,我所键入的信息,的确有发送出去。但我们爬贴吧的时候,可以不用理睬这些,因为浏览贴吧是以get方式就能完成一切。

        回到之间,kw的值,其实是被url编码过后展示出来的,我们可以通过urllib来实现这个效果:

import urllib

data = {
        "kw":"李毅",
        "ie":"utf-8",
        "pn":"50",
        }
kw = urllib.urlencode(data)
print kw
# 输出结果:
# ie=utf-8&kw=%E6%9D%8E%E6%AF%85&pn=50

        同样,urlencode接受的参数是字典形式,而我们需要传递的参数就可以得到浏览器所需要的格式了。之后进行拼接成完整的url就可以访问了。

4. 利用Chrom对网页分析

        回忆一下,当我们需要查看图片时,是不是需要进入帖子才行,而翻页只是为我们提供了更多可以选择的帖子标题。所以,接下来我们就需要对标题以及帖子的url进行分析。Chrome浏览器,右键—>检查—>任意标题:


    进入这个帖子查看url:

    

    按照这个方法多分析几个,就可以发现进入帖子的url其实就是“https://tieba.baidu.com”+a标签中href的值,如何取href的值呢?这里就需要xpath,建议使用xPath player插件,在网页进行区配:

from lxml import etree

html = etree.HTML(response.read())
a_href = html.xpath("....") #对应的xPath规则
返回的a_href是个列表,遍历取出即可。同样的,进入帖子之后的翻页也可以同相同的方法:


(注:我测试的时候,发现贴吧返回的页面主题部分被注释掉了,就会出现明明在页面上用插件区配到了结果,但是实际运行的代码返回的是空列表,所以可以替换掉页面中的注释符号:response.read().replace("<!--", " ").replace("-->", " ")

       最后,就该分析照片了,用前面的方法即可。

5. 避免反爬策略:

        事实上,保不齐需要爬取的目标网站有反扒策略,那么应对方法这里介绍三个:

        1. 更换服务器访问:即有策略的改变"User-Agent",

ua_list = [
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv2.0.1) Gecko/20100101 Firefox/4.0.1",
                    "Mozilla/5.0 (Windows NT 6.1; rv2.0.1) Gecko/20100101 Firefox/4.0.1",
                    "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.8.131 Version/11.11",
                    "Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11"
                ]

        2. 休眠:

import time

time,sleep(10)

        3. 使用代理ip:

         代理ip的请求方式与不使用代理ip不同,但区别很小

# 不使用代理
http = urllib2.HTTPHandler()
opener = urllib2.build_opener(http) 

# 使用代理
proxy_http = urllib2.ProxyHandler("http":"192.168.121.111:8080")
opener = urllib2.build_opener(http)


结尾:

        最后,贴上完整的代码,但是并不是每个贴吧都适用,更多的区别在于不同贴吧中,源代码标签名不同。所以同样的xpath规则可能会得不到想要的结果。需要根据实际情况做出适当的调整。

        事实上,在爬虫当中更多不是技术上的问题,而是策略上的问题。只要是能通过浏览器获得的数据,都可通过爬虫的方式来得到,所以在爬虫与反爬虫中,相信就目前而言,胜利的一定是前者。

# coding:utf-8

import urllib
import urllib2
from lxml import etree
import random
import time

class Down_image(object):
    """
    下载需要的,贴吧的图片
    """
    def __init__(self, tieba_name, start_page, end_page):
        self.kw = tieba_name
        self.s_p = int(start_page)
        self.e_p = int(end_page)
        self.header = {
                "Accept-Language" : "zh-CN,zh;q=0.9",
                "Connection" : "keep-alive",
                "Host" : "tieba.baidu.com",
                }
        self.ua_list = [
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv2.0.1) Gecko/20100101 Firefox/4.0.1",
                    "Mozilla/5.0 (Windows NT 6.1; rv2.0.1) Gecko/20100101 Firefox/4.0.1",
                    "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.8.131 Version/11.11",
                    "Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11"
                ]
        self.times = 0

    def start(self):
        """
        执行一系列代码的按钮
        """
        self.build_url()

    def build_url(self):
        """
        构建目的贴吧的url
        """
        # 构建页数的url
        for p in range(self.s_p-1, self.e_p):
            url = "https://tieba.baidu.com/f?"
            page_data = {
                    "kw":self.kw,
                    "pn":p*50,
                    }

            page_data = urllib.urlencode(page_data)
            ful_url = url + page_data
            
            self.get_html(ful_url)
    

    def get_html(self, url):
        """
        获取html,并且解析获得帖子的内容
        """
        handler = urllib2.HTTPHandler()
        self.opener = urllib2.build_opener(handler)

        request = urllib2.Request(url, headers = self.header)
        #获取随即user_agent,并且添加在请求报文里
        user_agent = random.choice(self.ua_list)
        request.add_header("User-Agent", user_agent)

        response = self.opener.open(request)
        html = response.read().replace("<!--", "").replace("-->", "")
        
        selector = etree.HTML(html)
        links = selector.xpath('//div[@class="threadlist_lz clearfix"]/div/a[@class="j_th_tit "]/@href')

        for l in links:
            tz_link = "http://tieba.baidu.com" + l + "?pn=1"
            self.get_image(tz_link)
    
    def get_image(self, link):
        """
        通过传递的帖子的link,进入相应的链接里面来,并且取得图片的链接
        """
        request = urllib2.Request(link, headers = self.header)
        
        user_agent = random.choice(self.ua_list)
        request.add_header("User-Agent", user_agent)
        
        html = self.opener.open(request).read().replace("<!--", "").replace("-->", "")
        selector = etree.HTML(html)
        
        # 如果是第一页,那么就要进行页数的区分
        if link[-1] == str(1):
            page_link = selector.xpath('//li[@class="l_pager pager_theme_4 pb_list_pager"]/a/@href')
            if page_link:
                # 根据对网页的代码分析,得到的翻页实现方法,对应做出的策略
                for p in page_link[:-2]:
                    p = "http://tieba.baidu.com" + p
                    self.get_image(p)
        # 落落贴/美女吧等
        image_link = selector.xpath('//div[@class="d_post_content j_d_post_content "]/img[@class="BDE_Image"]/@src')
        # 校花吧
        # image_link = selector.xpath('//div[@class="d_post_content j_d_post_content  clearfix"]/img[@class="BDE_Image"]/@src')
        for i_l in image_link:
            i_l = i_l.replace("https", "http")
            self.times += 1
            if self.times%10 == 0:
                print "休息一会"
                time.sleep(10)
            self.load_image(i_l)

    def load_image(self, link):
        """
        将图片下载到本地
        """ 
        filename = link[-10:]
        print filename+"正在下载..."
        
        # request = urllib2.Request(link, headers = self.header)
        image = urllib2.urlopen(link).read()
        # user_agent = random.choice(self.ua_list)
        # request.add_header("User-Agent", user_agent)
        # image = self.opener.open(request)
        
        with open("girl/"+filename, "wb") as ob:
            ob.write(image)
       
        print filename + "下载完成"


def main():
    
    tieba_name = raw_input("输入你想爬取的贴吧:")
    start_page = raw_input("输入你想爬去的起始页数:")
    end_page = raw_input("输入你想爬去的结束页数:")
    
    down_image = Down_image(tieba_name, start_page, end_page)
    down_image.start()
    print "结束"

if __name__ == "__main__":
    main()


猜你喜欢

转载自blog.csdn.net/qq_41359051/article/details/79843733