玩命加载中 . . .

python爬取网页学习笔记


本文仅涉及一些网页爬取工具的简单应用。利用python的urllib、requests、BeautifulSoup等库尝试实现了简单的爬取百度图片、必应图片/每日壁纸、豆瓣评论等功能。

一、工具库介绍

1. urllib 包

  • 介绍:Python urllib 库用于操作网页 URL,并对网页的内容进行抓取处理
  • urllib—URL处理模块
  • 模块:
    • urllib.request - 打开和读取 URL。
    • urllib.error - 包含 urllib.request 抛出的异常。
    • urllib.parse - 解析 URL。
    • urllib.robotparser - 解析 robots.txt 文件。

2. requests 库

  • 介绍:基于urllib,采⽤Apache2 Licensed开源协议的 HTTP 库
  • 官方文档
  • 主要有7个方法
    • Requests库主要有7个方法,分别是
      1. requests.request():构造一个请求,支撑以下各方法的基础方法
      2. requests.get():获取HTML网页的主要方法,对应于HTTP的GET
      3. requests.head():获取HTML网页头信息的方法,对应于HTTP的HEAD
      4. requests.post():向HTML网页提交POST请求的方法,对应于HTTP的POST
      5. requests.put():向HTML网页提交PUT请求的方法,对应于HTTP的PUT
      6. requests.patch():向HTML网页提交局部修改请求,对应于HTTP的PATCH
      7. requests.delete():向HTML页面提交删除请求,对应于HTTP的DELETE

3. BeautifulSoup

  • 介绍:提供一些简单的、python式的函数用来处理导航、搜索、修改分析树等功能。它是一个工具箱,通过解析文档为用户提供需要抓取的数据。
  • 文档

二、爬取百度/必应图片

1. 分析下载链接

  • 打开百度图片,输入关键词如”风景壁纸”,开发者工具->Network->抓包工具XHR。鼠标向下滑动,可得到图片的请求报文

    image-20210805205941341

    • 这里https://image.baidu.com/search/acjson?是我们需要的链接,问号后面的都是对应的参数信息
  • 同时可得到浏览器的请求报文头部信息(Request Headers),可用于模拟浏览器的请求操作,以应对网站的安全验证。这里百度图片仅验证User-Agent

    image-20210805210255889

2. 代码实现

  • 引入相关库

    import re
    import requests
    import urllib
    from time import sleep
  • 定义类并初始化

    class GetBaiduImage:
        
        def __init__(self, url, path, keyword, page):
            self.url = url
            self.path = path
            self.keyword = keyword
            self.page = page
            self.pn = 1                  # pn代表从第几张图片开始获取,百度图片下滑时默认一次性显示30张
            # self.first = 1              # 代表从第几张图片开始获取,必应图片下滑时默认一次性显示30张
  • 模拟浏览器拉取数据

    • 注意:这里的param 就是刚刚抓包得到链接的参数部分,一个都不能少
    def GetHtmlText(self):
        # 根据传入的url请求网站,并返回得到的数据
        param = {
            'tn': 'resultjson_com',
            'logid': ' 7517080705015306512',
            'ipn': 'rj',
            'ct': '201326592',
            'is': '',
            'fp': 'result',
            'queryWord': self.keyword,
            'cl': '2',
            'lm': '-1',
            'ie': 'utf-8',
            'oe': 'utf-8',
            'adpicid': '',
            'st': '',
            'z': '',
            'ic': '',
            'hd': '',
            'latest': '',
            'copyright': '',
            'word': self.keyword,
            's': '',
            'se': '',
            'tab': '',
            'width': '',
            'height': '',
            'face': '',
            'istype': '',
            'qc': '',
            'nc': '1',
            'fr': '',
            'expermode': '',
            'force': '',
            'cg': 'star',
            'pn': self.pn,
            'rn': '30',
            'gsm': '1e',
        }
    # =============================================================================
    #         # first 参数控制从第 first 张图开始(与百度图片的 pn 参数类似)
    #         param = {
    #             'q': self.keyword,
    #             'first': self.first,
    #             'count': '30',
    #             'cw': '1177',
    #             'ch': '593',
    #             'relp': '35',
    #             'tsc': 'ImageBasicHover',
    #             'datsrc': 'I',
    #             'layout': 'RowBased_Landscape',
    #             'mmasync': '1',
    #             'dgState': '',
    #             'IG': '9D7CBFCB39434C9583EA8186FB044DAA',
    #             'SFX': '3',
    #             'iid': 'images.5571'
    #             }
    # =============================================================================
        header = {
            'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
        }
        try:
            # 模拟浏览器拉取数据,防止被识别为爬虫并禁掉
            html_res = requests.get(self.url, headers=header, params=param)
            html_res.encoding = html_res.apparent_encoding
            html_res.raise_for_status()
            # html_res是响应response,text方法返回字符串形式的html,而content方法是直接拉取得到的字节类型数据,还需要手动解码为字符串
            # 当text()显示不正常时使用content.decoding('utf-8/gbk/gbk2312')
            html_text = html_res.content.decode('utf-8')          
        except:
            print('网页获取失败:', html_res.status_code)
            return None
        return html_text
  • 分析拉取到的html文本(变量html)并获取图片的下载链接

    • 分析发现thumbURL对应的就是图片下载链接

    image-20210805212258112

    def GetImageUrls(self):
        html = self.GetHtmlText()
        with open('test.txt', 'w', encoding='utf-8') as f:
            f.write(html)
            # urls = re.findall('murl":"(.*?)"', html, re.S)     # 必应图片
            urls = re.findall('"thumbURL":"(.*?)"', html, re.S)     # 百度图片
        return urls
  • 下载图片

    def Download(self):
        name = 1
        for n in range(int(page)):   
            img_urls = self.GetImageUrls()
            for img_url in img_urls:
                try:
                    urllib.request.urlretrieve(img_url, path+'%s.jpg'%name)
                except Exception:
                    print("下载失败!")
                else:
                    sleep(0.5)
                    print('成功下载第%s张图片' % (str(name)))
                    name += 1
            # 下载下一页
            self.pn += 29	   # 百度图片
            # self.first += 29	# 必应图片
  • main函数

    if __name__ == '__main__':
        url = 'https://image.baidu.com/search/acjson?'
        path = 'E:/学习资料/Python/code/百度图片/'
        keyword = '风景壁纸'            # 想要搜索的图片
        page = '2'                     # 想要爬取几页
        imgobj = GetBaiduImage(url, path, keyword, page)
        imgobj.Download()

3. 缺陷

  • 对于爬取必应图片,将上面的注释取消,并注释对应部分即可
  • 百度图片这种方法只能爬取缩略图
  • 必应图片可爬取高清大图,但每隔几张就会爬取失败。对应代码中“下载失败”的异常处理

4. 源代码

三、爬取必应的每日壁纸

已经有人搜集了必应的每日壁纸:必应每日高清壁纸。并提供了直接下载,因此我们直接从该网站上爬取图片。

1. 分析下载链接

  • 页数

    • 分析发现每一页对应的链接应该是:https://bing.ioliu.cn/?p={n}。n对应页数。所以我们可以找到总页数并将其提取出来

      image-20210805215127596

  • 图片链接

    • 这里直接获取下载链接并存为二进制文件。或者也可以像第一节那样获取图片链接并使用urllib.request.urlretrieve()函数下载

    image-20210805215515224

  • 获取图片信息,并将之作为图片名

    image-20210805220452156

2. 代码实现

  • 引入相关库

    import re
    import requests
    from bs4 import BeautifulSoup
    from time import sleep
    import os
  • 模拟浏览器拉取数据

    def GetHtmlText(self, url):
        # 根据传入的url请求网站,并返回得到的数据
        try:
            # 模拟浏览器拉取数据,防止被识别为爬虫并禁掉
            user_agent = {'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36'}
            html_text = requests.get(url, headers = user_agent)
            html_text.encoding = html_text.apparent_encoding
            html_text.raise_for_status()
            except:
                print('网页获取失败:', html_text.status_code)
                return None
        return html_text
  • 获取最大页数

    def GetMaxPageCount(self):
        # 获取主页信息,并且获取网站的最大页数 
        soup = BeautifulSoup(self.GetHtmlText(self.url).text, "html.parser")
        tag_page = soup.find('div', {'class':'page'})
        page_txt = None
        for tag_child in tag_page.children:
            if(tag_child.name == 'span'):
                page_txt = tag_child.string
                match = re.search(r'(?<=1 / )\d*', page_txt)
                max_page_count = int(match.group(0))
        return max_page_count
  • 存储图片

    def SavePictureInUrl(self, pic_url, pic_name):
        # 根据传入的url链接获取图片的二进制数据,并且根据传入的路径和文件名将文件写入到对应的路径中
        source = self.GetHtmlText(pic_url)
        if source == None:
            return 
        file_name = '{}.jpg'.format(pic_name)
        try:
            with open(self.pic_path+file_name, 'wb') as f:
                f.write(source.content)
                sleep(0.005)
                except OSError:
                    print("此图片下载失败!")
  • 下载一页的图片

    def GetOnePageJpg(self, page_count):
        # 从返回的网页数据中获取每张图片的相关信息以及图片下载的url,然后调用相关函数下载图片
        url = 'https://bing.ioliu.cn/?p={}'.format(page_count)
        soup = BeautifulSoup(self.GetHtmlText(url).text, 'html.parser')
        tag_container = soup.find_all('div', {'class':'container'})
        tag_item = tag_container[1]
        for tag_pic in tag_item.children:
            # 获取图片的标题和日期信息并且拼接成图片名
            tag_title = tag_pic.find('h3')
            text_title = tag_title.string
            a = re.findall(r'[^\*"/:?\\|<>]', text_title, re.S)      #剔除某些不能作为文件名的特殊字符
            text_title = ''.join(a)
            tag_calendar = tag_pic.find('p', {'class':'calendar'})
            tag_em = tag_calendar.find('em')
            text_calendar = tag_em.string
            text_pic_name = text_calendar + '__' + text_title
            if(os.path.exists(self.pic_path+text_pic_name+'.jpg')):
                continue
            # 获取图片的下载url
            tag_download = tag_pic.find('a', {'class':'ctrl download'})
            pic_url = self.url + tag_download['href']
            #信息保存到图片中
            self.SavePictureInUrl(pic_url, text_pic_name)
            print('.', end='', flush=True)        #输出进度信息
  • 下载所有页的图片

    def GetAllPageJpg(self):
        # 爬取所有的图片,并保存在输入的路径参数下
        max_page_count = self.GetMaxPageCount()
        for page_index in range(1, max_page_count):
            self.GetOnePageJpg(page_index)
            print('\r', '正在获取,已完成:{:.2f} %'.format(page_index/max_page_count*100), end = '', flush=True)      #输出进度信息
  • main函数

    def main():
        url = 'https://bing.ioliu.cn/'     
        pic_path = 'E:/学习资料/Python/code/必应每日壁纸/'  
        getBingImg = GetBingImage(url, pic_path)
        getBingImg.GetAllPageJpg()   
        
    if __name__ == '__main__':
        main()

3. 源代码

四、爬取豆瓣评论

1. 分析下载链接

  • 《怒火重案》的全部影评

  • 获取完整评论链接。分析发现每篇影评在此链接显示并不完整,要完整链接需要再次跳转。

    image-20210805221421635

  • 获取下一页的链接。发现每一页都是由原始链接和?start={n}拼接得到。该信息也可匹配得到,由属性next对应

    image-20210805222302884

  • 通过完整评论链接获取全文内容及其它相关信息(属性article对应内容)

    image-20210805221711519

    • 同时在这儿我们可以获取标题、作者和日期等相关信息。

2. 代码实现

  • 引入相关库

    import re
    import requests
    from bs4 import BeautifulSoup
    from time import sleep
  • 模拟浏览器拉取数据

    def GetHtmlText(self, url):
        # 根据传入的url请求网站,并返回得到的数据
        try:
            # 模拟浏览器拉取数据,防止被识别为爬虫并禁掉
            user_agent = {'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36'}
            html_text = requests.get(url, headers = user_agent)
            html_text.encoding = html_text.apparent_encoding
            html_text.raise_for_status()
        except Exception:
            print('网页获取失败:', html_text.status_code)
            return None
        return html_text
  • 获取完整评论链接和下一页链接

    def GetNextPageAndCompleUrls(self, url):
        # 1.获取下一页评论的链接
        # 2.获取完整评论的链接
    
        # 获取主页信息,并且获取下一页评论的尾部链接
        soup = BeautifulSoup(self.GetHtmlText(url).text, "html.parser")
        tag_next = soup.find('span', {'class':'next'})
        match = re.search('href="(.*?)"', str(tag_next))
        next_page = self.url + match.group(0)[6:-1]        # 拼接下一页url如"?start=20"
    
        # 获取该页所有评论的完整评论链接
        tag_main = soup.find_all('div', {'class':'main-bd'})
        comple_urls = re.findall('<a href="(.*?)">', str(tag_main))
        return next_page, comple_urls
  • 爬取所有评论并存储

    def SaveComments(self, url):
        # 爬取所有评论并储存
    
        count = 1                           # 评论条数
        for i in range(self.pages):
            source = self.GetNextPageAndCompleUrls(url)
            url = source[0]                     # 下一页的链接
            comple_urls = source[1]             # 所有的评论链接,是一个大小为20的列表
    
            for comple_url in comple_urls:
                soup = BeautifulSoup(self.GetHtmlText(comple_url).content.decode('utf-8'), "html.parser")
                # 获取评论相关信息
                tag_article = soup.find('div', {'class':'article'})
                # 作者
                author = re.search('<span>(.*?)</span>',str(tag_article)).group(0)[6:-7]
                # 时间
                time = re.findall('<span class="main-meta".*?>(.*?)</span>', str(tag_article))[0]
                # 标题
                title = re.findall('<span property="v:summary">(.*?)</span>', str(tag_article))[0]
                # 正文
                content = re.findall('<p data-page="0">(.*?)</p>', str(tag_article), re.S)
                # 拼接并存储
                head = "标题: " + title + '\n' + "作者: " + author + '\n' + "日期: " + time + '\n'
                content = '\n'.join(content)
                article = head + content
                with open(self.path, 'a') as f:
                    f.write('--------------第{}条评论-----------------------\n'.format(count))
                    f.write(article)
                    f.write('\n\n')
                    print("写入第{}条评论".format(count))
                    count += 1
                    sleep(0.5)
  • main函数

    def main():
        url = 'https://movie.douban.com/subject/30174085/reviews'     
        path = 'E:/学习资料/Python/code/豆瓣评论/comments.txt'  
        pages = 3
        getDoubanComments = GetDoubanComments(url, path, pages)
        getDoubanComments.SaveComments(url)
    
    if __name__ == '__main__':
        main()

3. 缺陷

4. 源代码

五、参考链接


文章作者: hjd
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hjd !
评论
  目录