一个简单地爬虫教程

功能:爬取目标网站全部主要图片(例子中是美图录网站的全部写真图片,按人名分类)

本示例使用Python3.5,需要额外安装BeautifulSoup 4

BeautifulSoup 4 安装方法:

Linux:
sudo apt-get install python-bs4

Mac:
sudo easy_install pip
pip install beautifulsoup4

Windows:
下载源码后,
python setup.py install
或者:
pip install beautifulsoup4

具体安装方式见:点这里

分析网站结构

目标网站“美图录”(别问我为什么选这个网站。。。百度上“随便”找的)

因为打算下载全部的网页图片,所以从最小的单元开始,也就是图片集(再小就是单一的图片了,也就可以直接下载了)

先打开首页,随便点开一个图片集,发现图片集的地址是这样的
http://www.meitulu.com/item/7487.html
在图片集中检查页面元素,如下所示

<div class="content">
  <center>
    ![[Ugirls尤果网] U181 陈雅漫 写真套图_第1页/第1张图](http://upload-images.jianshu.io/upload_images/2475481-8cdfce535296ab31.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  center>
  <center>
    ![[Ugirls尤果网] U181 陈雅漫 写真套图_第1页/第3张图](http://upload-images.jianshu.io/upload_images/2475481-43e882deb2d0c0cf.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  center>
  <center>
    ![[Ugirls尤果网] U181 陈雅漫 写真套图_第1页/第4张图](http://upload-images.jianshu.io/upload_images/2475481-2d5cb01257ad639e.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)    
  center>
  <center>
    ![[Ugirls尤果网] U181 陈雅漫 写真套图_第1页/第5张图](http://upload-images.jianshu.io/upload_images/2475481-8e773c5ec770c466.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  center>
div>

发现每一张主要图片资源链接都在center标签中,这样就可以在这个页面上提取图片链接并下载了
继续向下,发现下图所示,图片并不是存放在一个页面中的

而检查这里的html代码可以看到

<div id="pages">
  <a class="a1" href="http://www.meitulu.com/item/7487.html">上一页a>
  <span>1span>
  <a href="http://www.meitulu.com/item/7487_2.html">2a>
  <a href="http://www.meitulu.com/item/7487_4.html">4a>
  <a href="http://www.meitulu.com/item/7487_5.html">5a>
  <a href="http://www.meitulu.com/item/7487_6.html">6a>
  <a href="http://www.meitulu.com/item/7487_7.html">7a>
  <a href="http://www.meitulu.com/item/7487_8.html">8a>
  <a href="http://www.meitulu.com/item/7487_9.html">9a>
  <a href="http://www.meitulu.com/item/7487_10.html">10a>
  ".."
  <a href="http://www.meitulu.com/item/7487_16.html">16a>
  <a class="a1" href="http://www.meitulu.com/item/7487_2.html">下一页a>
div>

这个页面列表在class=”pages”的div标签中,当前页是用span标签装饰的,我们可以通过提取 下一页 按钮的链接来继续下载下一个页面的图片,但是我们怎么知道什么时候会到最后一页呢?点击最后一个页面的按钮,这里就是16页。再次检查这一部分的html代码

<div id="pages">
  <a class="a1" href="http://www.meitulu.com/item/7487_15.html">上一页a>
  <a href="http://www.meitulu.com/item/7487.html">1a>
  ".."
  <a href="http://www.meitulu.com/item/7487_7.html">7a>
  <a href="http://www.meitulu.com/item/7487_8.html">8a>
  <a href="http://www.meitulu.com/item/7487_9.html">9a>
  <a href="http://www.meitulu.com/item/7487_10.html">10a>
  <a href="http://www.meitulu.com/item/7487_11.html">11a>
  <a href="http://www.meitulu.com/item/7487_12.html">12a>
  <a href="http://www.meitulu.com/item/7487_13.html">13a>
  <a href="http://www.meitulu.com/item/7487_14.html">14a>
  <a href="http://www.meitulu.com/item/7487_15.html">15a>
  <span>16span>
  <a class="a1" href="http://www.meitulu.com/item/7487_16.html">下一页a>
div>

从这段代码中可以看到,下一页 按钮的链接指向的是16页,也就是当前页,而前面的页面指向的都是当前页的下一页。所以我们可以利用这一点来判断是否到最后一页。这样我们就有了下载一个完整图片集的思路了。

下面我们看看如何获得所有图片集的链接


发现网站首页有一个图集分类,我们可以认为他把网站上所有的资源都放在这里分好类了,随便点开一个分类,可以看到里面有排列整齐的图集,检查html代码

<div class="boxs">
  <ul class="img">
    <li>
      <a href="http://www.meitulu.com/item/8144.html" target="_blank">
        ![[尤蜜荟] 可乐Vicky 苏梅岛旅拍 第二刊 ~[43]](http://upload-images.jianshu.io/upload_images/2475481-1915763ae66ee8ad.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
      a>
      <p><span>3span>图片: 43 张(1600X2400)p>
      <p>机构:
        <a href="http://www.meitulu.com/t/tgod/" target="_blank" class="tags">推女神a>
      p>
      <p>模特:
        <a href="http://www.meitulu.com/t/kele-vicky/" target="_blank" class="tags">可乐Vickya>
      p>
      <p>标签:
        <a href="http://www.meitulu.com/t/jipin/" target="_blank" class="tags">极品a>
        <a href="http://www.meitulu.com/t/nvshen/" target="_blank" class="tags">女神a>
        <a href="http://www.meitulu.com/t/qingxin/" target="_blank" class="tags">清新a>
        <a href="http://www.meitulu.com/t/qingchun/" target="_blank" class="tags">清纯a>
        <a href="http://www.meitulu.com/t/weimei/" target="_blank" class="tags">唯美a>
        <a href="http://www.meitulu.com/t/huwai/" target="_blank" class="tags">户外a>
        <a href="http://www.meitulu.com/t/yangyan/" target="_blank" class="tags">养眼a>
      p>
      <p class="p_title">
        <a href="http://www.meitulu.com/item/8144.html" target="_blank">[尤蜜荟] 可乐Vicky 苏梅岛旅拍 第二刊 ~a>
      p>
    li>
    "..."
    
  ul>
div>

从这段代码中可以发现,所有的图集被放在了

标签中,每个图集的信息中包含模特名字,发行机构和一系列的标签。每一个信息对应一个链接,链接中是包含对应信息的图集的分类页面,这里我们按照人名分类,所以只要检索 模特 关键字就可以了。
这段页面的下方也是一个页面列表,检查html元素会发现与图集的列表模式相同。

另外,对于某些只有一个图集的人来说,他没有对应的分类页面,对于这些人要另外处理

小结

根据这些特征,我们遍历分类页面中的所有图集,通过字典记录人名对应的链接,如果遇到没有分类页面的人,则直接创建文件夹,下载图集。这样我们前期的分析工作就完成了,下面

开始写爬虫吧

先初始化几个要用到的全局变量

categaries = {}                        # 分类列表
person = {}                            # 人名列表
PATH = os.getcwd()                     # 根目录路径
forbidchar = r'<|>|/|\\|\||:|"|\*|\?'  # 系统禁止用作文件名的字符,正则表达式

一、图片下载函数

首先,我们要下载网站上所有的图片,所以需要有一个给定图片链接就能下载下来的函数:

def downloadimg(link, name):            # link为图片链接,name为图片名字
    data = urlopen(link, timeout=10)    # 打开连接
    tname = name+".jpg"                 # 给图片命名
    with open(tname, "ab") as code:     # 以追加二进制模式打开文件,并保存数据
        code.write(data.read())
    print(tname+" is done.")            # 打印提示文字

但这还不够,因为经常会碰到链接没有响应的情况,所以加上异常处理

def downloadimg(link, name):
    name = re.split(forbidchar, name)
    name = '.'.join(name)               # 通过re模块的split,将windows不支持的文件名符号,全部换成'.'
    for i in range(10):
        time.sleep(0.5)                 # 照顾别人服务器的带宽,适当加点延时。。。加多少看你心情
        try:
            data = urlopen(link, timeout=10)
            tname = name+".jpg"
            with open(tname, "ab") as code:
                code.write(data.read())
            print(tname+" is done.")
            break
        except Exception:
            time.sleep(3)               # 多数情况下,上面的语句中只有urlopen会出现无响应的异常,这时等待三秒,重新发送请求

二、图集下载函数

更进一步的,我们要处理一个给定链接的图集,首先我们写一个下载当前页面的主要图片的功能

def downloaditem(link, ):
    html = urlopen(link, timeout=100)          # 打开链接
    bsObj = BeautifulSoup(html, "html.parser") # 用bs解析html

    for center in bsObj.findAll("center"):     # 找到所有的center标签
        for img in center.findAll("img"):      # 找到其中包含img标签的
            boola = downloadimg(img.attrs['src'], img.attrs['alt'])
                                               # 下载image,并以图片的alt属性内容给图片命名

但这还没完,记得前面提到的页面列表么,我们还要继续下载 下一页 的图片。于是继续

def downloaditem(link, ):
    html = urlopen(link, timeout=100)
    bsObj = BeautifulSoup(html, "html.parser")

    for center in bsObj.findAll("center"):
        for img in center.findAll("img"):
            boola = downloadimg(img.attrs['src'], img.attrs['alt'])
#---------------------------------------------------------------------------
    page = bsObj.find("div", {"id":"pages"})   # 找到所有id属性为pages的div标签
    for a in page.findAll("a", {"class":"a1"}):
                                               # 找到其中class属性为a1的a标签
        if re.search(re.compile(r'下一页'), a.getText()):
                                               # 如果标签内容包含下一页
            number = re.search(re.compile(r"http://www\.meitulu\.com/.*?_([0-9]*?)\.html"), a.attrs['href'])
                                               #用正则表达式匹配链接中的页码
            if number:                         #如果匹配成功,失败时number为None
                link = number.group(0)         #提取页面链接
                number = number.group(1)       #提取页码
                if number != page.find('span').getText():
                                               #如果链接的页码跟当前页码不同,则不是最后一页,
                    print("download deeper...")#输出提示信息
                    downloaditem(link)         #继续下载下一页

完善一下代码,添加异常捕捉和延时

def downloaditem(link, ):
    for i in range(10):
        time.sleep(1)
        try:
            html = urlopen(link, timeout=100)
            break
        except Exception:
            print("Url Erroe")
            time.sleep(2)

    for i in range(10):
        try:
            bsObj = BeautifulSoup(html, "html.parser")
            break
        except Exception:
            print("Soup Error")

    for center in bsObj.findAll("center"):
        for img in center.findAll("img"):
            boola = downloadimg(img.attrs['src'], img.attrs['alt'])

    time.sleep(2)
    page = bsObj.find("div", {"id":"pages"})
    for a in page.findAll("a", {"class":"a1"}):
        if re.search(re.compile(r'下一页'), a.getText()):
            number = re.search(re.compile(r"http://www\.meitulu\.com/.*?_([0-9]*?)\.html"), a.attrs['href'])
            if number:
                link = number.group(0)
                number = number.group(1)
                if number != page.find('span').getText():
                    print("download deeper...")
                    downloaditem(link)

三、获取人名分类下的所有图集链接

def downloadperson(link, name):
    name = re.split(forbidchar, name)
    name = '.'.join(name)                      # 跟图片文件名原理一样,替换被禁止的字符
    personitems = {}

    if not os.path.exists(name):               # 检查这个人的文件夹之前有没有创建
        os.mkdir(name)                         # 如果没有就创建一个
    os.chdir(name)                             # 进入这个目录

    html = urlopen(link, timeout=100)          # 打开链接
    bsObj = BeautifulSoup(html, "html.parser") # 用bs解析


    for boxs in bsObj.findAll("div", {"class":"boxs"}): # 找到装载图片集的
标签 for li in boxs.findAll("li"): # 处理每一个图片集 for p in li.findAll('p', {"class":"p_title"}): # 找到包含图片链接的p标签 psn = p.find('a') personitems[psn.getText()] = psn.attrs['href'] # 用文件名作为key给字典添加图集链接 PATHtmp = os.getcwd() # PATHtmp是这一层人名文件夹的路径 for key in personitems: # 遍历字典,下载每一个图集 print('\n', "downloading ", key, '\n') if not os.path.exists(key): # 检验文件夹是否存在 os.mkdir(key) os.chdir(key) # 进入文件夹 downloaditem(personitems[key]) # 下载图集 os.chdir(PATHtmp) # 回到上一层目录,这里用的绝对路径,避免中途被打断导致后面的下载也出现错误 os.chdir(PATH) # 回到根目录

完善代码,添加异常捕捉和延时,这里因为同一个人没有发现有多页的情况,所以没有处理页面列表的代码

def downloadperson(link, name):
    name = re.split(forbidchar, name)
    name = '.'.join(name)
    personitems = {}

    if not os.path.exists(name):
        os.mkdir(name)
    os.chdir(name)

    for i in range(10):
        time.sleep(1)
        try:
            html = urlopen(link, timeout=100)
            break
        except Exception:
            time.sleep(2)
            print("Url Erroe")

    for i in range(10):
        try:
            bsObj = BeautifulSoup(html, "html.parser")
            break
        except Exception:
            print("Soup Error")

    for boxs in bsObj.findAll("div", {"class":"boxs"}):
        for li in boxs.findAll("li"):
            try:
                for p in li.findAll('p', {"class":"p_title"}):
                    print('\n',p,'\n')
                    psn = p.find('a')
                    personitems[psn.getText()] = psn.attrs['href']
            except:
                print("Find Error")

    PATHtmp = os.getcwd()
    for key in personitems:
        print('\n', "downloading ", key, '\n')
        if not os.path.exists(key):
            os.mkdir(key)
        os.chdir(key)
        downloaditem(personitems[key])
        os.chdir(PATHtmp)

    os.chdir(PATH)

四、获得分类下所有人名的分类链接

def getperson(link,):
    for i in range(10):
        time.sleep(1)
        try:
            html = urlopen(link, timeout=100)           # 打开连接
            break
        except Exception:
            time.sleep(2)
            print("Url Erroe")

    for i in range(10):
        try:
            bsObj = BeautifulSoup(html, "html.parser")  # bs解析
            break
        except Exception:
            print("Soup Error")

    for boxs in bsObj.findAll("div", {"class":"boxs"}): # 获取分类下包含图集的标签
        for li in boxs.findAll("li"):                   # 逐个图集处理
            try:
                for a in li.findAll('p'):
                    print(a.getText())                  # 输出图集提示信息
                    name = re.search(re.compile(r'^模特:(.*?)$'), a.getText())
                    if name:
                        psn = a.find('a')               # 尝试查找人名分类页面链接
                        person[psn.getText()] = psn.attrs['href']
            except:                                     # 如果找不到分类页面,则直接下载图集
                print("downloading item..."+name.group(1))
                item = li.find('p', {"class":"p_title"}).find("a")
                print(item.getText())
                if not os.path.exists(name.group(1)):
                    os.mkdir(name.group(1))             # 创建人名文件夹
                os.chdir(name.group(1))                 # 进入人名文件夹
                print(name.group(1))
                name = item.getText()                   # 提取图集
                name = re.split(forbidchar, name)       # 处理图集名(文件夹名)
                name = '.'.join(name)
                if not os.path.exists(name):
                    os.mkdir(name)                      # 创建图集文件夹
                os.chdir(name)                          # 进入图集文件夹
                downloaditem(item.attrs['href'])        # 下载图集
                os.chdir(PATH)                          # 回到根目录


    time.sleep(3)                                       # 延时
    page = bsObj.find("div", {"id":"pages"})            # 处理下一页问题,原理同downloaditem函数
    for a in page.findAll("a", {"class":"a1"}):
        if re.search(re.compile(r'下一页'), a.getText()):
            number = re.search(re.compile(r"http://www\.meitulu\.com/t/.*?([0-9]*?)\.html"), a.attrs['href'])
            link = number.group(0)
            number = number.group(1)
            if number != page.find('span').getText():
                print("scrap deeper...")
                getperson(link)
                break

五、主函数

if __name__ == "__main__":
    for i in range(10):
        time.sleep(1)
        try:
            html = urlopen("http://www.meitulu.com", timeout=100) # 打开首页链接
            break
        except Exception:
            print("Url Erroe")
            time.sleep(2)

    for i in range(10):
        try:
            bsObj = BeautifulSoup(html, "html.parser")            # bs解析
            break
        except Exception:
            print("Soup Error")

    for a in bsObj.find("li", {"id":"tag"}).find("ul", {"id":"tag_ul"}).findAll("a"):
        categaries[a.getText()] = a.attrs['href'] # 获取所有分类首页的链接,以分类名为key

    for key in categaries:
        time.sleep(3)
        print(i,"loading page..."+key)
        getperson(categaries[key])                # 获取每一个分类下的所有人名链接

    for key in person:
        downloadperson(person[key], key)          # 下载每一个人名下的所有图集

总结

完整代码在这里:simplespider.py
我在代码中延时加的比较多,所以运行起来有些慢,但毕竟这只是个练习,照顾一下别人服务器比较好= =。

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