python爬虫个人笔记

第一章:爬虫基础简介

什么是爬虫:

  • 通过编写程序,模拟浏览器上网,然后让其去互联网上去抓取数据的过程。

爬虫的价值:

  • 实际应用

  • 就业

爬虫究竟是合法的还是违法的?

  • 本身在法律中不被禁止

  • 具有违法风险

  • 善意爬虫 恶意爬虫

爬虫带来的风险:

  1. 干扰了网站的正常的运营,恶意爬取网站信息

  2. 爬虫抓去了受到法律保护的特定类型的数据信息

如何在编写使用爬虫合法化

  • 时常优化自己的程序,避免干扰被访问网站的正常运行

  • 在使用传播到的数据时,需要审查抓取到的内容,如果发现涉及到用户隐私或者商业机密等敏感内容,需要及时停止爬取或传播

爬虫在使用场景中的分类

  • 通用爬虫

    • 抓取系统重要组成部分。抓取的时一整张页面数据。

  • 聚焦爬虫

    • 是建立在通用爬虫的基础之上。抓取的是页面中特定或指定的的局部内容。

  • 增量式爬虫

    • 检测网站中数据更新的情况。只会爬取网站中最新更新出来的数据。

爬虫的矛与盾

  • 有的网站希望被爬,有的不希望。

反爬机制

  • 门户网站,可以通过制定相应的策略或者技术手段,防止爬虫程序进行网站数据的爬取。

反反爬策略

  • 爬虫程序也可以通过执行的相关的策略或者技术手段,用来破解门户网站中具备的反爬机制,从而可以获取门户网站中相关的策略。

robots.txt协议

  • 君子协议明确规定了网站中哪些数据可以被爬虫爬取,那些内容不可以被爬虫爬取。

HTTP协议

  • 概念:服务器和客户端进行数据交互的一种形式。

常用请求头信息

  • User-Agent:请求载体的身份标识。

  • Connection:请求完毕后是断开连接还是保持连接。

常用响应头信息

  • Content-Type:服务器响应回客户端的数据类型。

HTTPS协议

  • 安全的超文本传输(http)协议

加密方法

  • 对称密钥加密

  • 非对称密钥加密

  • 证书密钥加密

第二章:requests模块基础

基于网络请求的两个模块:

  • requests模块(简洁高效)

  • urllib模块(比较古老)

requesets模块:python中原生的一款基于网络请求的模块

  • 功能强大

  • 使用简单便捷

  • 处理爬虫操作效率极高

  • 作用:

    • 用来模拟浏览器发请求

  • 如何使用:(requests模块的编码流程)

    • 指定url

    • 发起请求

    • 获取响应数据

    • 持久化存储

  • 环境安装:

     pip install requests
  • 实战编码:

    • 需求:爬取搜狗首页的页面数据

    • 01.requests第一血.py

    •  import requests
       
       
       def main():
           # 1.指定url:https://www.sogou.com/
           url = "https://www.sogou.com/"
           # 2.发起请求
           # get方法会返回一个响应对象命名为response
           response = requests.get(url=url)
           # 3.获取响应数据
           # 返回一组字符串,即网页源代码
           page_text = response.text
           print(page_text)
           # 4.持久化存储
           with open('./sogou.html', 'w', encoding="utf-8") as fp:
               fp.write(page_text)
           print("爬取数据结束")
       
       
       if __name__ == '__main__':
           main()
  • 实战巩固:

    • 需求:爬取搜狗指定词条对应的所搜索结果页面(简易网页采集器)

      • UA检测:门户网站的服务器会检测对应请求载体身份,如果检测到的请求的载体身份表示是一款浏览器,就说明该请求为正常请求,但是如果检测到的请求的载体身份表示不是基于浏览器的,表示该请求为不正常请求(爬虫),则服务器端很有可能拒绝该请求

      • UA伪装:让爬虫对应的请求载体身份标识伪装成某一款浏览器

      • 02.requests实战之网页采集器.py

      •  # UA:User-Agent(请求载体的身份标识)
         # UA检测:门户网站的服务器会检测对应请求载体身份,如果检测到的请求的载体身份表示是一款浏览器,
         # 就说明该请求为正常请求,但是如果检测到的请求的载体身份表示不是基于浏览器的,
         # 表示该请求为不正常请求(爬虫),则服务器端很有可能拒绝该请求
         
         # UA伪装:让爬虫对应的请求载体身份标识伪装成某一款浏览器
         import requests
         
         
         def main():
             # UA伪装:将对应User-Agent封装到一个字典中
             headers = {
                 'User-Agent':"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"
            }
             # 指定url
             url = "https://sogou.com/web"
             # 处理url携带的参数:封装到字典中
             kw = input("Enter a word for search:")
             param = {
                 "query": kw
            }
             # 发送请求,对指定的url发起的请求对应的url是携带的参数的,并且请求过程中处理了参数
             response = requests.get(url=url, params=param, headers=headers)
         
             page_text = response.text
             filename = kw + '.html'
             with open(filename, 'w', encoding="utf-8") as fp:
                 fp.write(page_text)
             print(filename + "保存成功!")
         
         
         if __name__ == '__main__':
             main()
    • 需求:破解百度翻译https://fanyi.baidu.com/

      • POST请求(携带了参数)

      • 响应数据是一组JSON数据

      • 03.requests实战之破解百度翻译.py

      •  import json
         import requests
         
         
         def main():
             # 进行UA伪装
             headers = {
                 'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"
            }
             # 指定url
             post_url = "https://fanyi.baidu.com/sug"
         
             # 动态翻译
             kw = input("Enter a word for translation:")
         
             # post请求参数处理(同get请求一致)
             data = {
                 "kw": kw
            }
         
             # 请求发送
             response = requests.post(url=post_url, data=data, headers=headers)
         
             # 获取响应数据,直接返回一个对象obj,如果确认响应数据时json类型才能使用json()方法
             dic_obj = response.json()
             print(dic_obj)
             # 进行持久化存储
             fp = open("./" + kw + ".json", 'w', encoding="utf-8")
             json.dump(dic_obj, fp=fp, ensure_ascii=False)  # 拿到的中文数据,不能使用ascii码
             fp.close()
             print("success")
         
         
         if __name__ == '__main__':
             main()
    • 需求:爬取豆瓣电影分类排行榜https://movie.douban.com/中的电影详情数据

      • 04.requests实战之豆瓣电影爬取.py

      •  import requests
         import json
         
         
         def main():
             headers = {
                 'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"
            }
             url = "https://movie.douban.com/j/chart/top_list"
             param = {
                 "type": "24",
                 "interval_id": "100:90",
                 "action": "",
                 "start": "0",  # 表示从第几部开始取
                 "limit": "20",  # 表示取的个数
            }
             response = requests.get(url=url, params=param, headers=headers)
         
             # 获取数据
             list_data = response.json()
         
             # 持久化存储
             fp = open('./douban.json', 'w', encoding='utf-8')
             json.dump(list_data, fp=fp, ensure_ascii=False)
             fp.close()
             print("successful!")
         
         
         if __name__ == '__main__':
             main()
    • 需求:爬取肯德基餐厅查询http://www.kfc.com.cn/kfccda/index.aspx中指定地点的位置数据

      • 05.requests实战之肯德基餐厅位置爬取

      •  import requests
         
         
         def main():
             headers = {
                 'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"
            }
             url = "http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword"
             city = input("Enter a city:")
             data = {
                 'cname': '',
                 'pid': '',
                 'keyword': city,
                 'pageIndex': "1",
                 'pageSize': "10",
            }
         
             response = requests.post(url=url, data=data, headers=headers)
             data_text = response.text
             filename = city + "_kfc.txt"
             with open(filename, 'w', encoding='utf-8') as fp:
                 fp.write(data_text)
         
         
         if __name__ == '__main__':
             main()
    • 需求:爬取国家药品监督管理总局中基于中华人民共和国化妆品生产许可证相关数据http://scxk.nmpa.gov.cn:81/xk/ (暂时不会爬)

      • 动态加载数据

      • 首页中对应的企业信息数据是通过Ajax动态请求到的

      • 通过对详情页的url观察发现:

        • url域名一样,只有id参数不一样

        • id值可以从首页对应的Ajax请求的json串中获取

        • 域名和id值可以通过拼接出一个完整的详情页的url

        • 详情页中的详情数据也是动态加载出来的

第三章:数据解析

三种爬虫

聚焦爬虫:爬取页面中指定的页面内容。

  • 编码流程:

    • 指定url

    • 发起请求

    • 获取响应数据

    • 数据解析

    • 持久化存储

数据解析分类:

  • 正则

  • bs4

  • xpath(重点)

数据解析原理概述:

  • 解析的局部的文本内容都会在标签之间或标签对应的属性中进行存储

  • 1、进行指定标签的定位

  • 2、标签或者标签对赢得属性中存储的数据值进行提取(解析)

正则解析:

  • 利用正则表达式提取整个页面中需要的内容进行爬取

bs4:

  • 数据解析的原理:

    1. 标签定位

    2. 提取标签、标签属性中存储的数据值

  • bs4数据解析的原理:

    1. 实例化一个BeautifulSoup对象,并且将页面源码数据加载到该对象中

    2. 通过调用BeautifulSoup对象中的相关属性或方法进行标签定位或数据提取

  • 环境安装

    • pip install bs4

    • pip install lxml

  • 如何实例化BeautifulSoup

    • from bs4 import BeautifulSoup

    • 对象的实例化:

      1. 将本地html文档中的数据加载到该对象中

        •  # 将本地的html文档中的数据加载到该对象中
           fp = open("./test.html", 'r', encoding="utf-8")
           soup = BeautifulSoup(fp, "lxml")
      2. 将互联网上和获取的页面源码加载到该对象中

        •  page_text = response.text
           soup = BeautifulSoup(page_text,'lxml')
    • 提供的用于数据解析的方法和属性

      • soup.<标签名> 返回的是html中第一次出现标签名的代码段

      • soup.find("<标签名>")

        1. 第一种用法同soup.<标签名>

        2. 第二种用法:属性定位

          1. 根据具体的属性定位到具有此属性的某标签的代码段

          2. ex:soup.find("div", class_/id/attr = 'song')# 表示具有song属性的div标签代码段

      • soup.find_all(“<标签名>”) # 以列表形式-返回符合要求的所有标签代码表

      • soup.select()函数

        1. 参数是选择器(id选择器,类选择器,标签选择器)以列表形式返回代码段

        2. 层级选择器如下:

        3.  <div class="tang">
              <ul>
                 <li><a href="http://www.baidu.com" title="qing">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村</a></li>
                 <li><a href="http://www.163.com" title="qin">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山</a></li>
                 <li><a href="http://www.126.com" alt="qi">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君</a></li>
                 <li><a href="http://www.sina.com" class="du">杜甫</a></li>
                 <li><a href="http://www.dudu.com" class="du">杜牧</a></li>
                 <li><b>杜小月</b></li>
                 <li><i>度蜜月</i></li>
                 <li><a href="http://www.haha.com" id="feng">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘</a></li>
              </ul>
           </div>
        4. # 执行下面代码
          soup.select('div.tang > ul > li > a')[0] # > 表示一个层级
          # 返回值:
          # <a href="http://www.baidu.com" title="qing">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村</a>

          soup.select('div.tang a')[0] # 也可以用空格表示多个层级关系
      • 获取标签之间的文本数据

        • soup.<标签名>/其他获取标签函数.text/string/get_text()

        • string 只可以获取该标签下面直系文本内容

        • text和get_text()方法可以获取某个标签中所有的文本内容,即使文本内容不属于该标签直系内容

        • ex:

        • <div class="tang">
          <ul>
          <li><a href="http://www.baidu.com" title="qing">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村</a></li>
          <li><a href="http://www.163.com" title="qin">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山</a></li>
          <li><a href="http://www.126.com" alt="qi">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君</a></li>
          <li><a href="http://www.sina.com" class="du">杜甫</a></li>
          <li><a href="http://www.dudu.com" class="du">杜牧</a></li>
          <li><b>杜小月</b></li>
          <li><i>度蜜月</i></li>
          <li><a href="http://www.haha.com" id="feng">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘</a></li>
          </ul>
          </div>
        • # 执行下面代码
          soup.select('div.tang a')[0].get_text()
          # 输出:清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村

          soup.select('div.tang')[0].text
          # 输出:
          # 清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村
          # 秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山
          # 岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君
          # 杜甫
          # 杜牧
          # 杜小月
          # 度蜜月
          # 凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘

          soup.select('div.tang')[0].string
          # 输出:None
        • 获取标签中属性值

          • ex:soup.a[‘href’]

案例:

import requests
from bs4 import BeautifulSoup

# 需求:爬取三国演义中所有章节标题和章节内容(三国爬不成,爬个百年孤独)
def main():
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36 Core/1.77.119.400 QQBrowser/10.9.4817.400'
}
url = "https://www.tianyashuku.com/waiguo/2464/"

# 对首页的页面数据进行爬取
page_text = requests.get(url=url, headers=headers).text.encode('ISO-8859-1')

# 在首页中解析出章节标题和详情页的url
# 1.实例化BeautifulSoup对象,需要将页面源码数据加载到该对象中
soup = BeautifulSoup(page_text, 'lxml')
# 解析章节标题和详情页的url
li_list = soup.select('.book-list > ul > li')
fp = open('./bainiangudu.txt', 'w', encoding='utf-8')
for li in li_list:
title = li.a.string
detail_url = "https://www.tianyashuku.com" + li.a['href']
# 详情页发起请求,解析出章节内容
chapter_content = requests.get(url=detail_url, headers=headers).text.encode('ISO-8859-1')
# 解析详情页中相关的章节内容
detail_soup = BeautifulSoup(chapter_content, 'lxml')
div_tag = detail_soup.find('div', class_='mb2')
# 解析到了章节的具体内容
content = div_tag.text
fp.write(title + ":" + content + '\n')
print(title, '爬取成功!')


if __name__ == '__main__':
main()

xpath解析:

最常用且最便捷高效的一种解析方式。通用性最强。

  • xpath解析原理:

    1. 实例化etree对象,且需要将被解析的页面源码数据加载到该对象中。

    2. 调用etree对象中的xpath方法结合xpath表达式实现标签的定位和内容的捕获。

  • 环境安装:

    • pip install lxml

  • 如何实例化一个etree对象: from lxml import etree

    1. 将本地html文档中源码数据加载到etree对象中

      • etree.parse(filePath)

    2. 可以将从互联网上获取的页面源码数据加载到该对象中

      • etree.HTML(page_text)

    3. xpath(“xpath表达式”)

      • xpath表达式:

        1. /:表示的是从根节点开始定位,表示的是一个层级

        2. //:表示的是多个层级。可以从任意位置开始定位

        3. 属性定位–//div[@class=‘song’]:表示的是class属性值为song的div标签(tag[@attrName=‘attrValue’])

        4. 索引定位–////div[@class='song']/p[3]: 索引是从1开始的

        5. 取文本:

          • /text()指的是标签中直系的文本内容(列表形式)

          • //text()指的是标签中所有的文本内容(非直系内容)(列表形式)

        6. 取属性:/@attrNmae ======> img/@src

      • 示例代码:

      • <html lang="en">
        <head>
        <meta charset="UTF-8" />
        <title>测试bs4</title>
        </head>
        <body>
        <div>
        <p>rozi</p>
        </div>
        <div class="song">
        <p>李清照</p>
        <p>王安石</p>
        <p>苏轼</p>
        <p>柳宗元</p>
        <a href="http://www.song.com/" title="赵匡胤" target="_self">
        <span>this is span</span>
        宋朝是最强大的王朝,不是军队的强大,而是经济很强大,国民都很有钱</a>
        <a href="" class="du">总为浮云能蔽日,长安不见使人愁</a>
        <img src="http://www.baidu.com/meinv.jpg" alt="" />
        </div>
        <div class="tang">
        <ul>
        <li><a href="http://www.baidu.com" title="qing">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村</a></li>
        <li><a href="http://www.163.com" title="qin">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山</a></li>
        <li><a href="http://www.126.com" alt="qi">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君</a></li>
        <li><a href="http://www.sina.com" class="du">杜甫</a></li>
        <li><a href="http://www.dudu.com" class="du">杜牧</a></li>
        <li><b>杜小月</b></li>
        <li><i>度蜜月</i></li>
        <li><a href="http://www.haha.com" id="feng">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘</a></li>
        </ul>
        </div>
        </body>
        </html>
      • from lxml import etree


        def main():
        # 实例化etree对象且将被解析的源码加载到该对象中
        tree = etree.parse('./test.html')
        # 第一个/表示从根节点定位,一个/表示一个层级,//表示多个层级,/类似于bs4中的 ">" 。
        # title = tree.xpath("/html/head/title")

        # div = tree.xpath("/html/body/div")
        # print(title)
        # # 输出:[<Element title at 0x20e4de7ca48>]

        # print(div)
        # # 输出:[<Element div at 0x20e4de7cb48>, <Element div at 0x20e4de7cb88>, <Element div at 0x20e4de7cbc8>]

        # r = tree.xpath("/html//div")
        # print(r)
        # # 输出:[<Element div at 0x20e4de7cb48>, <Element div at 0x20e4de7cb88>, <Element div at 0x20e4de7cbc8>]

        # 找到源码中所有的div标签。
        # r = tree.xpath("//div")
        # print(r)
        # # 输出:[<Element div at 0x20e4de7cb48>, <Element div at 0x20e4de7cb88>, <Element div at 0x20e4de7cbc8>]

        # 定义到指定的div.song
        # r = tree.xpath("//div[@class='song']")
        # print(r)
        # # 输出:[<Element div at 0x2782265d9c8>]

        # 定位到苏轼所对应的p标签(第三个p标签)
        # r = tree.xpath("//div[@class='song']/p[3]")
        # print(r)
        # # 输出:[<Element div at 0x2782265d9c8>]

        # 定位到杜牧,返回的是一个列表,需要加[0]
        # r = tree.xpath("//div[@class='tang']//li[5]/a/text()")[0]
        # print(r)
        # 输出:杜牧

        # r = tree.xpath("//div[@class='tang']//li[7]/text()")
        # print(r)
        # 输出:[]

        # r = tree.xpath("//div[@class='tang']//li[7]//text()")[0]
        # print(r)
        # # 输出:度蜜月

        # r = tree.xpath("//div[@class='tang']//text()")
        # print(r)
        # # 输出:['\n\t\t', '\n\t\t\t', '清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村',
        # '\n\t\t\t', '秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山', '\n\t\t\t',
        # '岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君', '\n\t\t\t', '杜甫', '\n\t\t\t',
        # '杜牧', '\n\t\t\t', '杜小月', '\n\t\t\t', '度蜜月', '\n\t\t\t', '凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,
        # 晋代衣冠成古丘', '\n\t\t', '\n\t']

        # 取属性 src ex:<img src="http://www.baidu.com/meinv.jpg" alt="" />
        # r = tree.xpath("//div[@class='song']/img/@src")[0]
        # print(r)
        # # 输出:http://www.baidu.com/meinv.jpg


        if __name__ == '__main__':
        main()

案例:

  • 需求:爬取58二手房相关房源信息

  • import requests
    from lxml import etree


    # 需求:爬取58二手房相关房源信息
    def main():
    headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36'
    }
    url = 'https://bj.58.com/ershoufang/'
    page_text = requests.get(url=url, headers=headers).text

    # 数据解析
    # 实例化etree对象
    tree = etree.HTML(page_text)
    house_list = tree.xpath("//div[@class='property-content']/div[@class='property-content-detail']/div"
    "[@class='property-content-title']//h3/text()")
    with open('house_info.txt', 'w', encoding='utf-8') as fp:
    for house_title in house_list:
    fp.write(house_title + "\n")
    print(house_title + "成功写入文件!")
    print("已全部写入文件。")


    if __name__ == '__main__':
    main()
  • 需求:解析下载图片数据 https://pic.netbian.com/4kmeinv/(全是美女)

  •  import requests
     from lxml import etree
     import os
     
     # 需求:解析下载图片数据 https://pic.netbian.com/4kmeinv/ =.=
     def main():
         if not os.path.exists('./beauties'):
             os.mkdir('./beauties')
         headers = {
             'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36'
        }
         url = "https://pic.netbian.com/4kmeinv/"
         response = requests.get(url=url, headers=headers)
         # 防止中文乱码,根据网页源代码meta属性,修改编码
         # response.encoding = 'gbk'
         page_text = response.text
     
         # 解析src的属性值,alt属性值
         tree = etree.HTML(page_text)
     
         li_list = tree.xpath("//div[@class='slist']/ul/li")
         for li in li_list:
             # ./ 表示li标签
             img_src = "https://pic.netbian.com/" + li.xpath('./a/img/@src')[0]
             img_name = li.xpath('./a/img/@alt')[0].encode("iso-8859-1").decode('gbk') + ".jpg"
             # 较为通用处理中文乱码的解决方案,我这里直接在上面写了
             # img_name.encode("iso-8859-1").decode('gbk')
             # print(img_name)
             # 请求图片进行持久化存储,content返回的是二进制数据,存储二进制数据就用这种
             img_data = requests.get(url=img_src, headers=headers).content
             img_path = 'beauties/' + img_name
             with open(img_path, 'wb') as fp:
                 fp.write(img_data)
                 print('图片' + img_name + "爬取成功!")
         print('succeed!')
     
     
     if __name__ == '__main__':
         main()
  • 需求:全国城市名称爬取https://www.aqistudy.cn/historydata/

  •  import requests
     from lxml import etree
     
     
     # 项目需求:解析出所有城市名称 https://www.aqistudy.cn/historydata/
     def main():
         ############################################方案一:分别解析###########################################################
         # headers = {
         #     'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36'
         # }
         # url = "https://www.aqistudy.cn/historydata/"
         # page_text = requests.get(url=url, headers=headers).text
         #
         # tree = etree.HTML(page_text)
         # # 热门城市的名称
         # hot_li_list = tree.xpath('//div[@class="bottom"]/ul/li')
         #
         # all_cities = []
         # for li in hot_li_list:
         #     hot_cities = li.xpath("./a/text()")[0]
         #     all_cities.append(hot_cities)
         #
         # # 全部城市的名字
         # all_li_list = tree.xpath("//div[@class='bottom']/ul/div[2]/li")
         # for li in all_li_list:
         #     single_city = li.xpath("./a/text()")[0]
         #     all_cities.append(single_city)
         # print(all_cities)
         # print(len(all_cities))
     
         ############################################方案二:通用解析###########################################################
     
         headers = {
             'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36'
        }
         url = "https://www.aqistudy.cn/historydata/"
         page_text = requests.get(url=url, headers=headers).text
     
         tree = etree.HTML(page_text)
         # 解析热门城市和所有城市对应的a标签
         # //div[@class='bottom']/ul/li/a                     热门城市a标签的层级关系
         # //div[@class='bottom']/ul/div[2]/li/a             全部城市a标签的层级关系
         a_list = tree.xpath("//div[@class='bottom']/ul/li/a | //div[@class='bottom']/ul/div[2]/li/a")  # 使用|连接两个xpath表达式
     
         all_city = []
         for a in a_list:
             city_name = a.xpath('./text()')[0]
             all_city.append(city_name)
             print(city_name + " 添加成功")
         print(all_city)
         print(len(all_city))
     
     
     if __name__ == '__main__':
         main()
  • 需求:下载站长素材中免费简历模板 https://sc.chinaz.com/jianli/free.html

  • import requests
    from lxml import etree
    import os


    # 需求:下载站长素材中免费简历模板 https://sc.chinaz.com/jianli/free.html
    def main():
    if not os.path.exists('./resumes'):
    os.mkdir('./resumes')
    headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36'
    }
    url = "https://sc.chinaz.com/jianli/free.html"
    page_text = requests.get(url=url, headers=headers).text
    tree = etree.HTML(page_text)
    # 根据网站源代码进行解析
    a_list = tree.xpath("//div[@class='sc_warp mt20']/div[@id='main']/div[@id='container']//a[@class='title_wl']")
    for a in a_list:
    a_href = "https:" + a.xpath("./@href")[0]
    single_text = requests.get(url=a_href, headers=headers).text
    single = etree.HTML(single_text)
    # 获取页面名字作为文件名字
    file_name = single.xpath("//title/text()")[0].encode("iso-8859-1").decode('utf-8') + '.rar'
    # 解析每个具体页面的下载链接
    download_a_list = single.xpath("//div[@id='down']//ul[@class='clearfix']/li/a")
    # 选取一个下载链接下载即可
    download_url = download_a_list[3].xpath("./@href")[0]
    # 获取二进制数据
    file_data = requests.get(url=download_url, headers=headers).content

    # 设置文件存储地址,进行持久化存储
    file_path = './resumes/' + file_name
    with open(file_path, 'wb') as fp:
    fp.write(file_data)
    print(file_name + " 爬取成功!")

    print('DONE!')


    if __name__ == '__main__':
    main()

第四章:验证码

验证码和爬虫之间的关系:

  • 反爬机制:验证码,识别验证码图片中的数据,用于模拟登陆操作

    • 人工肉眼识别(不推荐)

    • 第三方自动识别(推荐)

      • 使用python第三方库OCR识别(有些平台比如超级鹰付费提供服务,但付钱是不可能付钱的)

      • 去github下载Tesseract-OCR:https://github.com/UB-Mannheim/tesseract/wiki(steam++加速)

      • 下载安装。注意:安装的时候选中中文包(安装时把所有选项都勾上)

      • ...\Lib\site-packages\pytesseract\pytesseract.py

        找到文件:tesseract_cmd = 'tesseract' 修改为:tesseract_cmd = 'C:\Program Files\Tesseract-OCR\tesseract.exe'

  • 实战:识别古诗文登陆页面的验证码。

    • 使用第三方模块识别验证码的流程:

      1. 将验证码图片进行本地下载

      2. 识别图片得到验证码字符串

    • 识别可能会出错,多识别几次就OK

    • import pytesseract
      from PIL import Image
      import requests
      from lxml import etree


      def get_rand_code(image_path):
      """封装成获取验证码的方法"""
      # 导入图片文件的地址
      image = Image.open(image_path)
      return pytesseract.image_to_string(image)


      def main():
      headers = {
      'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36'
      }
      url = "https://so.gushiwen.cn/user/login.aspx?from=http://so.gushiwen.cn/user/collect.aspx"

      page_text = requests.get(url=url, headers=headers).text
      # 解析验证码img中src属性值
      tree = etree.HTML(page_text)
      rand_code_url = "https://so.gushiwen.cn/" + tree.xpath('//*[@id="imgCode"]/@src')[0]
      img_data = requests.get(url=rand_code_url, headers=headers).content
      # 将验证码图片下载到本地
      img_path = './RandCode.jpg'
      with open(img_path, "wb") as fp:
      fp.write(img_data)
      print(get_rand_code(img_path))



      if __name__ == '__main__':
      main()

第五章:requests模块高级

模拟登陆:

  • 爬取基于某些用户的用户信息

需求:模拟登录古诗文网

  • 点击登陆后,会发起一个post请求

  • post请求中会携带登陆之前录入的相关登录信息(账号,密码,验证码)

  • 验证码:每次请求都会动态变化

  • 流产(G)

  • import sys

    import pytesseract
    from PIL import Image
    import requests
    from lxml import etree


    def get_rand_code(image_path):
    """封装成获取验证码的方法"""
    # 导入图片文件的地址
    image = Image.open(image_path)
    code = pytesseract.image_to_string(image)
    if len(code) > 5:
    return '识别失败'
    else:
    return code[0:4]


    def main():
    headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36'
    }
    url = "https://so.gushiwen.cn/user/login.aspx?from=http://so.gushiwen.cn/user/collect.aspx"

    page_text = requests.get(url=url, headers=headers).text
    # 解析验证码img中src属性值
    tree = etree.HTML(page_text)
    rand_code_url = "https://so.gushiwen.cn/" + tree.xpath('//*[@id="imgCode"]/@src')[0]
    img_data = requests.get(url=rand_code_url, headers=headers).content
    # 将验证码图片下载到本地
    img_path = './RandCode.jpg'
    with open(img_path, "wb") as fp:
    fp.write(img_data)

    login_url = "https://so.gushiwen.cn/user/login.aspx?from=http%3a%2f%2fso.gushiwen.cn%2fuser%2fcollect.aspx"

    account = input("请输入账号:")
    pwd = input("请输入密码:")
    rand_code = get_rand_code(img_path)
    if rand_code == "识别失败":
    print("请重试!")
    sys.exit()

    print("验证码:" + rand_code)
    print("验证码长度:" + str(len(rand_code)))
    data = {
    '__VIEWSTATE': '3px9aH4XSR52/hfCO+lKZDGqCBNoRkwq8MdsxZnPD8K/minc+IHDVsSzTUULZX9YADL03qNfU92tF3mMiRbbe45xkJ1Rz5tTp1IpDujgACcgjNnStcg9pmioW5vnqV1GzbTM2mR7hW5LRb4KtuFtPr1vfFQ=',
    'from': 'http://so.gushiwen.cn/user/collect.aspx',
    'email': account,
    'pwd': pwd,
    'code': rand_code,
    'denglu': '登录',
    }

    login_page_text = requests.post(url=login_url, headers=headers, data=data).text
    with open('poem.html', 'w', encoding='utf-8') as fp:
    fp.write(login_page_text)


    if __name__ == '__main__':
    main()

需求:模拟登陆github(试了下这个案例,发现已经不行)

  • 虽然不可用,但是需要学习

    1. http/https协议特性:无状态

    2. 不设置cookie时,第二次基于个人页面请求的时候,服务器端不知道是基于登陆状态的请求

  • 解决方法:

    • cookie:用来让服务器端记录客户端的相关状态

      1. 手动处理:通过抓包工具获取cookie值,封装到headers里(不推荐)

      2. 自动处理:

        • cookie值的来源:模拟登陆post登陆后,由服务器端创建。

        • session会话对象:

          • 作用:

            1. 进行请求的发送

            2. 如果请求过程中产生了cookie,则该cookie会被自动存储/携带在该session对象中。

        • 创建session对象(session = requests.Session())

        • 使用session对象进行模拟登录post请求的发送(cookie就会被存储再session中)

        • session对象在个人主页对应的get请求进行发送(携带了session)

import requests
from lxml import etree


class Login(object):
def __init__(self):
"""初始化一些变量"""
self.headers = {
"Referer": "https://www.github.com/",
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36',

'Host': 'github.com'
}
self.login_url = 'https://www.github.com/login'
self.post_url = "https://www.github.com/session"
self.logined_url = "https://www.github.com/settings/profile"
self.session = requests.Session() # 使用requests.Session()维持一个会话,自动处理Cookies

def token(self):
"""
通过此页面获取初始的Cookies,提取authenticity_token
:return:authenticity_token
"""
response = self.session.get(url=self.login_url, headers=self.headers)
selector = etree.HTML(response.text)
# 解析出登陆所需的authenticity_token信息并返回
token = selector.xpath('//*[@id="login"]/form/input[2]/@value')[0]
return token

def login(self, email, password):
"""实现一个登陆方法"""

# email, password以参数形式传递
post_data = {
'commit': 'Sign in',
"authenticity_token": self.token(),
"login": email,
"password": password,
'utf8': '&#10003;',
}

# 使用session对象的post()方法模拟登陆,request自动处理的重定向信息,登陆成功后直接跳转到首页,再使用dynamics()方法处理
response = self.session.get(self.post_url, data=post_data, headers=self.headers)
if response.status_code == 200:
self.dynamics(response.text)

# 使用session对象请求个人详情页,调用profile方法处理个人详情页信息
response = self.session.get(self.logined_url, headers=self.headers)
if response.status_code == 200:
self.profile(response.text)

def dynamics(self, html):
selector = etree.HTML(html)
dynamics = selector.xpath("//div[contains(@class,'news')]//div[contains(@class,'alert')]")
for item in dynamics:
dynamic = ''.join(item.xpath(".//div[@class='title']//text()")).strip()
print(dynamic)

def profile(self, html):
selector = etree.HTML(html)
name = selector.xpath("//input[@id='user_profile_name']/@value")[0]
email = selector.xpat("//select[@id='user_profile_email']/option[@value!='']/text()")
print(name, email)


if __name__ == '__main__':
login = Login()
email = input("邮箱地址")
password = input("请输入密码")
login.login(email=email, password=password)

代理:

破解封IP这种反爬机制。

什么是代理:

  • 代理服务器。

作用:

  1. 突破自身IP访问限制。

  2. 隐藏自身真实IP

代理相关的网站:

  • 快代理

  • 西祠代理

代理IP类型:

  • http:应用到http协议对应的url中

  • https:应用到https协议对应的url中

代理ip匿名度:

  • 透明:服务器知道该次请求使用了代理,也知道真实IP

  • 匿名:服务器知道该次请求使用了代理,不知道真实IP

  • 高匿:服务器不知道该次请求使用了代理,更不知道真实的IP

第六章:高性能异步爬虫

目的:再爬虫中使用异步实现高性能数据爬取的操作。

异步爬虫的方式:

  • 多线程,多进程(不推荐):

    • 优点:可以为相关阻塞的操作单独开启线程或进程,阻塞操作就可以异步执行。

    • 弊端:无法无限制开启多线程或者多进程。

  • 线程池、进程池(适当使用):

    • 优点:可以降低系统对进程或者线程创建和销毁的频率,降低系统开销。

    • 弊端:池中进程或者线程的数量有上限。

  • 单线程+异步协程(推荐):

    • event_loop:事件循环,相当于一个无限循环,我们可以把一些函数注册到这个事件循环上,当满足某些条件的时候,函数就会被循环执行。

    • coroutine:协程对象,我们可以将协程对象注册到事件循环中,它会被事件循环调用。我们可以使用async关键字来定义一个方法,这个方法在调用时不会立即被执行,而是返回一个协程对象。

    • task:任务,它是对协程对象的进一步封装,包含了人物的各个状态。

    • future:代表将要执行或还没有执行的任务,实际上和task没有本质区别。

    • async:定义一个协程。

    • await:用来挂起阻塞方法的执行

线程池的基本使用
# import time
#
#
# def get_page(str):
# print("正在下载:", str)
# time.sleep(1)
# print('下载成功:', str)
#
#
# name_list = ['1', '2', '3', '4']
#
# start_time = time.time()
# for name in name_list:
# get_page(name)
#
# end_time = time.time()
#
# print("总共耗时%d" % (end_time - start_time))


import time
from multiprocessing.dummy import Pool

# 线程池实现
start_time = time.time()


def get_page(str):
print("正在下载:", str)
time.sleep(2)
print('下载成功:', str)


name_list = ['1', '2', '3', '4']

# 实例化一个线程池对象
pool = Pool(4)
# 将列表中每一个列表元素传递给get_page进行处理,传递一个可迭代对象作为第二个参数
pool.map(get_page, name_list)

end_time = time.time()

print("总共耗时:%d" % (end_time - start_time))
案例:下载梨视频中的视频
import requests
import random
import os
import re

from multiprocessing.dummy import Pool
from fake_useragent import UserAgent
from lxml import etree


class LiVideoSpider:
'''梨视频爬虫类'''

def __init__(self):
self.st_url = 'https://www.pearvideo.com/panorama' # url
self.p = Pool(4) # 创建4个线程的线程池
self.headers = {
'ua': UserAgent().random
}
self.items = [] # 存储视频相关信息,[{'id':'xxx','video_url':'xxx'...},...]
self.video_path = './梨视频' # 视频存储路径

def start(self):
'''爬虫入口'''

# 创建存储文件夹
if not os.path.exists(self.video_path):
os.mkdir(self.video_path)

# 获取主页数据
response = requests.get(url=self.st_url, headers=self.headers)
tree = etree.HTML(response.text)
li_list = tree.xpath('//*[@id="listvideoList"]/ul/li')

# 解析主页数据,提取id,详情页url,视频标题
for li in li_list:
title = li.xpath('.//div[@class="vervideo-title"]/text()')[0]
detail_url = li.xpath('./div/a/@href')[0]
id = detail_url.split('_')[-1]
item = {
'id': id,
'title': title + '.mp4',
'detail_url': detail_url
}
self.items.append(item)

# 构造每个视频ajax请求的url
for i in range(len(self.items)):
js_url = 'https://www.pearvideo.com/videoStatus.jsp?contId={}&mrd={}'.format(self.items[i]['id'],
random.random())
self.items[i]['js_url'] = js_url

# 通过线程池异步获取每个视频的下载地址url
items = self.p.map(self.get, self.items)

# 异步下载每个视频
self.p.map(self.download, items)

def get(self, item):
'''获取视频下载地址'''

# headers中添上Referer参数
headers = {
'ua': UserAgent().random,
'Referer': item['detail_url']
}
json_data = requests.get(url=item['js_url'], headers=headers).json()
video_url = json_data["videoInfo"]["videos"]["srcUrl"]
# 修正视频下载地址url
item['video_url'] = re.sub(r'16\d+-', r'cont-{}-'.format(item['id']), video_url)
return item

def download(self, item):
'''下载视频'''

video_data = requests.get(url=item['video_url'], headers=self.headers).content
with open(self.video_path + '/' + item['title'], 'wb') as f:
f.write(video_data)


if __name__ == '__main__':
LiVideoSpider().start()

第七章:动态加载数据处理

selenium模块的基本使用

  • selenium模块和爬虫间的的关联:

    • 便捷的获取网站中动态加载的数据

    • 便捷实现模拟登陆

  • 什么是selenium模块?

    • 基于浏览器自动化的一个模块。

  • selenium使用流程:

    • pip install selenium

    • 下载一个浏览器驱动程序(Google)

    • 实例化一个浏览器对象

    • 编写基于浏览器自动化的操作代码

      • 发起请求:get(url)

      • 标签定位:find系列的方法

      • 标签交互:send_key(‘xxx’)

      • 执行js程序:execute_script(‘jsCode’)

      • 前进,后退:back(), forward()

      • 关闭浏览器:quit()

    • selenium处理iframe

      • 如果定位的标签存在于iframe中,则必须使用switch_to.frame(id)

      • 动作链(拖动):from selenium.webdriver import ActionChains

        1. 实例化一个动作链对象:action = ActionChains(driver)

        2. click_and_hold(div):长按操作

        3. move_by_offset(x,y)

        4. perform()让动作链立即执行

        5. action.release()释放动作链

        6. perform()本质上是将动作链操作假如一个队列,perform表示立刻执行,也可以按顺序写好再action.perform()释放所有操作

        from selenium import webdriver
        from time import sleep
        # 动作链导包
        from selenium.webdriver import ActionChains

        driver = webdriver.Chrome(executable_path='./chromedriver.exe')
        driver.get("https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable")

        # 如果定位的标签是存在于iframe标签之中的则需要通过以下操作进行标签定位
        driver.switch_to.frame('iframeResult') # 切换到浏览器标签定位的作用域

        # 获取div标签
        div = driver.find_element_by_id("draggable")

        # 动作链
        action = ActionChains(driver)
        action.click_and_hold(div) # 长按

        for i in range(5):
        # perform()立即执行动作链操作
        action.move_by_offset(17, 0).perform()
        sleep(0.1)

        # 释放动作链
        action.release().release()

        print(div)

无头浏览器:

隐藏浏览器,不让浏览器弹出但依然可以驱动浏览器:

from selenium.webdriver.chrome.options import Options

chrome_options = Options()
chrome_options.add_argument("--headless")
chrome_options.add_argument('--disable--gpu')
driver = webdriver.Chrome('./chromedriver.exe', chrome_options=chrome_options)

规避检测:

# 实现规避检测

from selenium.webdriver import ChromeOptions

options = ChromeOptions()
options.add_experimental_option('excludeSwitches', ['enable-automation'])
options.add_argument('--disable-blink-features=AutomationControlled')
driver = webdriver.Chrome('./chromedriver.exe', chrome_options=chrome_options, options=options)

案例:模拟登录12306

from selenium.webdriver.chrome.options import Options
from selenium.webdriver import ChromeOptions
from selenium import webdriver
import time
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By

# 实现规避检测
options = ChromeOptions()
options.add_experimental_option('excludeSwitches', ['enable-automation'])
options.add_argument('--disable-blink-features=AutomationControlled') # 避免webdriver检测

# 无头浏览器
# chrome_options = Options()
# chrome_options.add_argument("--headless")
# chrome_options.add_argument('--disable--gpu')

# driver = webdriver.Chrome('./chromedriver.exe', chrome_options=chrome_options, options=options)

driver = webdriver.Chrome('./chromedriver.exe', options=options)
url = "https://kyfw.12306.cn/otn/resources/login.html"
driver.get(url)

# 查看是否被检测js代码
js = 'return window.navigator.webdriver'

# 从页面直接检查元素(账号登陆),然后复制xpath即可获取到当前按钮
btn = driver.find_element_by_xpath('//*[@id="toolbar_Div"]/div[2]/div[2]/ul/li[1]/a')
btn.click()
print(driver.execute_script(js))
time.sleep(2)

# 获取账号密码文本框标签
id_input = driver.find_element_by_id("J-userName")
pwd_input = driver.find_element_by_id("J-password")


# 获取用户用户账号密码信息
phone_num = input("请输入用户名、手机号或邮箱:")
pwd = input("请输入密码:")

# 输入文本框内容
id_input.send_keys(phone_num)
time.sleep(0.5)
pwd_input.send_keys(pwd)

# 获取登录按钮标签
login_button = driver.find_element_by_id("J-login")

# 点击登录按钮
login_button.click()

print(driver.execute_script(js))
time.sleep(2)

# 获取滑块的按钮Xpath
# btn = driver.find_element_by_xpath('//*[@id="nc_1_n1z"]') 下面方法更通用
btn = driver.find_element(By.XPATH, '//*[@id="nc_1_n1z"]')

# 滑动滑块
action = ActionChains(driver)
action.drag_and_drop_by_offset(btn, 300, 0).perform()

driver.quit()

第八章:scrapy框架

什么是框架?

  • 继承了很多功能并且具有很强通用性的项目模板。

如何学习框架?

  • 专门学习框架封装的功能的详细用法。

什么是scrapy?

  • 爬虫中封装好的一个明星框架。

  • 功能:

    • 高性能持久化存储

    • 异步数据下载

    • 高性能数据解析

    • 分布式

scrpay框架的基本使用

  • 环境的安装:

    • mac or linux:pip install scrapy

    • windows

      • pip install wheel

      • 下载twisted,下载地址为http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted

      • 安装twisted:pip install Twisted‑17.1.0‑cp36‑cp36m‑win_amd64.whl

      • pip install pywin32

      • pip install scrapy 测试:在终端里录入scrapy指令,没有报错即表示安装成功!

  • 创建工程:scrapy startproject xxxPro

  • 在spiders子目录中创建一个爬虫文件

  • 执行工程

    • scrapy crawl <spiderName>

    • 在settings里加上下列配置,只接受错误日志信息

      # 输出显示指定类型的日志信息
      LOG_LEVEL = "ERROR"
  • scrapy数据解析

  • import scrapy


    class DongqiudiSpider(scrapy.Spider):
    name = 'dongqiudi'
    # allowed_domains = ['www.dongqiudi.com']
    start_urls = ['http://www.dongqiudi.com/']

    def parse(self, response):
    """解析:热门资讯的标题和链接"""
    # //*[@id="__layout"]/div/div[2]/div/div[3]/div[2]/div[2]/div[2]/div/p[8]/a
    # //*[@id="__layout"]/div/div[2]/div/div[3]/div[2]/div[2]/div[2]/div/p[1]/a
    p_list = response.xpath('//*[@id="__layout"]/div/div[2]/div/div[3]/div[2]/div[2]/div[2]/div/p')
    for p in p_list:
    # xpath返回的列表,列表元素一定是Selector类型的对象
    # extract()可以将Selector对象中data参数存储的字符串提取出来,
    # 如果不指定索引,会返回列表形式的data数据,如果返回列表有多个data数据,可以使用join()转换成字符串形式
    # news_url = p.xpath("./a/@href")[0].extract()
    # 如果返回的列表只有一个列表元素,可以直接使用extract_first(),不需要指定索引
    news_url = p.xpath("./a/@href").extract_first()
    # news_url = "https://www.dongqiudi.com" + p.xpath("./a/@href")[0]
    news_title = p.xpath("./a/text()").extract()[0]
    print(news_url)
    print(news_title)
  • 在settings中进行UA伪装

    USER_AGENT = "*****"

    scrapy持久化存储

    1. 基于终端指令:

      • 要求:只可以将parse方法的返回值存储到本地的文本文件中(不能存到数据库)。

      • 指令:scrapy crawl <spiderName> -o <filePath>

      • 注意:持久存储对应的文本文件只能是:'json', 'jsonlines', 'jl', 'csv', 'xml', 'marshal', 'pickle'

      • 优点:简介、高效、便捷

      • 弊端:局限性比较强(数据只可以存储到指定类型文本文件中)

         

         import scrapy
         class DongqiudiSpider(scrapy.Spider):
             name = 'dongqiudi'
             # allowed_domains = ['www.dongqiudi.com']
             start_urls = ['http://www.dongqiudi.com/']
         
              def parse(self, response):
                  """解析:热门资讯的标题和链接"""
                  # //*[@id="__layout"]/div/div[2]/div/div[3]/div[2]/div[2]/div[2]/div/p[8]/a
                  # //*[@id="__layout"]/div/div[2]/div/div[3]/div[2]/div[2]/div[2]/div/p[1]/a
                  p_list = response.xpath('//*[@id="__layout"]/div/div[2]/div/div[3]/div[2]/div[2]/div[2]/div/p')
                  all_data = []
                  for p in p_list:
                      # xpath返回的列表,列表元素一定是Selector类型的对象
                      # extract()可以将Selector对象中data参数存储的字符串提取出来,
                      # 如果不指定索引,会返回列表形式的data数据,如果返回列表有多个data数据,可以使用join()转换成字符串形式
                      # news_url = p.xpath("./a/@href")[0].extract()
                      # 如果返回的列表只有一个列表元素,可以直接使用extract_first(),不需要指定索引
                      news_url = "https://www.dongqiudi.com" + p.xpath("./a/@href").extract_first()
                      # news_url = "https://www.dongqiudi.com" + p.xpth("./a/@href")[0]
                      news_title = p.xpath("./a/text()").extract()[0]
                      dic = {
                          "title": news_title,
                          "url": news_url
                      }
                      all_data.append(dic)
                      # print(news_url)
                      # print(news_title)
                  return all_data
             
         # 指令:scrapy crawl dongqiudi -o ./懂球帝热门推荐.csv
    2. 基于管道:

      • 编码流程:

        1. 数据解析

        2. 在item类中定义相关属性

        3.  import scrapy
           
           
           class DongqiudiproItem(scrapy.Item):
               # define the fields for your item here like:
               # name = scrapy.Field()
           
               title = scrapy.Field()  # 定义title
               url = scrapy.Field()  # 定义url
        4. 将解析的数据封装存储到item类型的对象中

        5. 将item类型的对象提交给管道进行持久化存储的操作(4、5👇)

        6.  import scrapy
           from dongqiudiPro.items import DongqiudiproItem
           
           class DongqiudiSpider(scrapy.Spider):
               name = 'dongqiudi'
               # allowed_domains = ['www.dongqiudi.com']
               start_urls = ['http://www.dongqiudi.com/']
               def parse(self, response):
                   """解析:热门资讯的标题和链接"""
                   # //*[@id="__layout"]/div/div[2]/div/div[3]/div[2]/div[2]/div[2]/div/p[8]/a
                   # //*[@id="__layout"]/div/div[2]/div/div[3]/div[2]/div[2]/div[2]/div/p[1]/a
                   p_list = response.xpath('//*[@id="__layout"]/div/div[2]/div/div[3]/div[2]/div[2]/div[2]/div/p')
                   for p in p_list:
                       # xpath返回的列表,列表元素一定是Selector类型的对象
                       # extract()可以将Selector对象中data参数存储的字符串提取出来,
                       # 如果不指定索引,会返回列表形式的data数据,如果返回列表有多个data数据,可以使用join()转换成字符串形式
                       # news_url = p.xpath("./a/@href")[0].extract()
                       # 如果返回的列表只有一个列表元素,可以直接使用extract_first(),不需要指定索引
                       news_url = "https://www.dongqiudi.com" + p.xpath("./a/@href").extract_first()
                       # news_url = "https://www.dongqiudi.com" + p.xpath("./a/@href")[0]
                       news_title = p.xpath("./a/text()").extract()[0]
                       item = DongqiudiproItem()
                       item['title'] = news_title
                       item['url'] = news_url
           
                       yield item  # 将item提交给管道
        7. 在管道类的process_item中要将其接收到的item对象中存储的数据进行持久化存储

        8.  from itemadapter import ItemAdapter
           
           
           class DongqiudiproPipeline(object):
               fp = None
           
               def open_spider(self, spider):
                   """
                      重写父类的方法
                      该方法只在开始爬虫的时候被调用一次
                  """
                   print("开始爬虫..........")
                   self.fp = open("./懂球帝热门.txt", 'w', encoding='utf-8')
           
               # 专门用来处理item类型对象
               # 该方法可以接收爬虫文件提交到的item对象
               # 该方法每接收到一个item就被调用一次
               def process_item(self, item, spider):
                   title = item['title']
                   url = item['url']
                   self.fp.write(title + ":" + url + '\n')
           
                   return item
           
               def close_spider(self, spider):
                   """
                  只会在爬虫结束时被调用一次
                  :return:
                  """
                   print("结束爬虫.......")
                   self.fp.close()
        9. 在配置文件中开启管道👇

        10. ITEM_PIPELINES = {
          'dongqiudiPro.pipelines.DongqiudiproPipeline': 300,
          # 300是优先级,数值越小优先级越高
          }
      • 优点:通用性强

      • 弊端:编码稍有繁琐

    3. 面试题:将爬取到的数据一份存储到本地,一份存储到数据库,如何实现?

      • 管道文件中一个管道类对应的是将数据存储到一种平台

      • 爬虫文件提交的item只会给管道文件中第一个内执行的管道类接收

      • process_item中的return item表示将item传递给下一个即将被执行的管道类

基于Spider的全站数据爬取

  • 将网站中某板块下的全部页码对应的页面数据进行爬取

  • 需求:爬取https://pic.netbian.com/4kmeinv/中图片名称

  • settings设置(老三样)

  • # Crawl responsibly by identifying yourself (and your website) on the user-agent
    USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"

    # Obey robots.txt rules
    ROBOTSTXT_OBEY = False

    # 只显示ERROR
    LOG_LEVEL = "ERROR"
  • 实现方式:

    • 将所有页面的url添加到start_url列表中(不推荐)

    • 自行手动进行请求发送(推荐)

      • 手动请求发送:

        • import scrapy


          class ScenerySpider(scrapy.Spider):
          name = 'scenery'
          # allowed_domains = ['www.xxx.com']
          start_urls = ['https://pic.netbian.com/4kmeinv/']

          # 生成一个通用的url模板(2~60页)
          url = "https://pic.netbian.com/4kmeinv/index_%d.html"
          page_num = 2

          def parse(self, response):
          li_list = response.xpath('//*[@id="main"]/div[3]/ul/li')
          for li in li_list:
          # //*[@id="main"]/div[3]/ul/li[1]/a/b
          img_name = li.xpath('./a/b/text()').extract_first()
          print(img_name)
          if self.page_num <= 60:
          new_url = self.url % self.page_num
          self.page_num += 1
          # 收到请求发送,callback回调函数专门用于数据解析
          yield scrapy.Request(url=new_url, callback=self.parse)

五大核心组件

  • 引擎(Scrapy)

    • 用来处理整个系统的数据流处理, 触发事务(框架核心)

  • 调度器(Scheduler)

    • 用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址

  • 下载器(Downloader)

    • 用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的)

  • 爬虫(Spiders)

    • 爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面

  • 项目管道(Pipeline)

    • 负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。

请求传参

  • 使用场景:如果爬取解析的数据不在同一张页面中。(深度爬取)

  • 需求:爬取boss岗位名称,岗位描述

    • 老三样

    • # Crawl responsibly by identifying yourself (and your website) on the user-agent
      USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"

      # Obey robots.txt rules
      ROBOTSTXT_OBEY = False

      # 只显示ERROR
      LOG_LEVEL = "ERROR"

图片数据爬取

  • 基于scrapy爬取字符串类型的数据和爬取图片类型的数据区别?

    • 字符串:只需要基于xpath进行解析且提交管道进行持久化存储

    • 图片:xpath解析出图片的src属性值。单独对图片地址发起请求获取二进制类型的数据

  • ImagesPipeline

    • 只需要将img的src的属性值进行解析,封装到item,提交到管道,管道就会对图片的src进行请求发送获取二进制数据,且还会帮我们进行持久化存储

  • 需求:爬取站长素材中的高清图片:https://sc.chinaz.com/tupian/

  • 使用流程:

    • 数据解析(图片地址)

      • import scrapy
        from imgsPro.items import ImgsproItem


        class ImgSpider(scrapy.Spider):
        name = 'img'
        # allowed_domains = ['www.xxx.com']
        start_urls = ['https://sc.chinaz.com/tupian/']

        def parse(self, response):


        # /html/body/div[3]/div[2]
        div_list = response.xpath('/html/body/div[3]/div[2]/div')
        # print(div_list)
        for div in div_list:
        # /html/body/div[3]/div[2]/div[1]/div/a
        # src = div.xpath('./div/a/@href').extract_first()
        # /html/body/div[3]/div[2]/div[5]/img
        src = "https:" + div.xpath('./img/@data-original').extract_first()
        # print(src)
        item = ImgsproItem()
        item['src'] = src
        yield item
        # print(item)
    • 将存储图片地址的item提交到指定的管道类

      • item = ImgsproItem()
        item['src'] = src
        yield item
    • 在管道文件中自制一个基于ImagePileLine的管道类

      • # 自己封装一个Image管道类
        from scrapy.pipelines.images import ImagesPipeline
        import scrapy
        class imgsPileLine(ImagesPipeline):

        #就是可以根据图片地址进行图片数据的请求
        def get_media_requests(self, item, info):

        yield scrapy.Request(item['src'])

        #指定图片存储的路径
        def file_path(self, request, response=None, info=None, *, item=None):
        imgName = request.url.split('/')[-1]
        return imgName

        def item_completed(self, results, item, info):
        return item #返回给下一个即将被执行的管道类
    • 从写管道类的三个方法

      • def get_media_requests(self, item, info): yield scrapy.Request(item['src'])

        #指定图片存储的路径 def file_path(self, request, response=None, info=None, *, item=None): imgName = request.url.split('/')[-1] return imgName

        def item_completed(self, results, item, info): return item #返回给下一个即将被执行的管道类

    • 在settings里开启管道,并且指定图片存储的目录结构

      •  

      • ITEM_PIPELINES = {
        'imgsPro.pipelines.imgsPileLine': 300,
        }
        #指定图片存储的目录
        IMAGES_STORE = './imgs_bobo'

中间件

  • 下载中间件

    • 位置:引擎和下载器之间

    • 作用:批量拦截到整个工程中所有的请求和响应

    • 拦截请求:

      • UA伪装:process_request

      • 代理IP:process_exception:return request

    • 拦截响应:

      • 篡改响应内容,响应对象

      • 需求:爬取腾讯体育新闻的新闻数据(新闻标题和对应内容)

        1. 通过网易新闻首页解析出五大板块对应的url(非动态加载)

        2. 每一个板块对应的新闻标题都是动态加载出来的(动态加载)

        3. 通过解析出每一条新闻详情页的url获取详情页的页面源码,解析出新闻内容

CrawlSpider:类,Spider的一个子类

  • 全站数据爬取的方式

    • 基于Spider:手动请求发送

    • 基于CrawlSpider

  • CrawlSpider的使用:

    • 创建一个工程

    • cd XXX

    • 创建爬虫文件(CrawlSpider):

      • scrapy genspider -t crawl spidername www.xxx.com

      • import scrapy
        from scrapy.linkextractors import LinkExtractor
        from scrapy.spiders import CrawlSpider, Rule


        class SunSpider(CrawlSpider):
        name = 'sun'
        # allowed_domains = ['www.xxx.com']
        start_urls = ['http://www.xxx.com/']

        # link:链接提取器,作为一个参数被作用在了规则解析器中
        link = LinkExtractor(allow=r'Items/')
        rules = (
        # Rule:规则解析器
        Rule(link, callback='parse_item', follow=True), # 实例化了一个规则解析器对象
        )

        def parse_item(self, response):
        item = {}
        # item['domain_id'] = response.xpath('//input[@id="sid"]/@value').get()
        # item['name'] = response.xpath('//div[@id="name"]').get()
        # item['description'] = response.xpath('//div[@id="description"]').get()
        return item
      • 链接提取器:

        • 作用:根据指定规则(allow)进行指定链接的提取

      • 规则解析器:

        • 作用:将链接提取器提取到的链接进行指定规则(callback)解析

分布式爬虫

  • 概念:我们需要搭建一个分布式机群,让其对一组资源进行分布联合爬取。

  • 作用:提高爬取数据的效率

  • 如何实现分布式?

    • 安装一个scrapy-redis的组件

    • 原生的scrapy不可以实现分布式爬虫的,必须要让scrapy结合着scrapy-redis组件一起实现分布式爬虫

    • 为什么原生的scrapy不可以实现分布式爬虫

      • 调度器不可以被分布式机群共享

      • 管道不可以被分布式机群共享

    • scrapy-redis组件作用:

      • 可以给原生的scrapy框架提供可以被共享的管道和调度器

    • 实现流程

      1. 创建工程

      2. 创建一个基于CrawlSpider的爬虫文件

      3. 修改当前爬虫文件

        • 导包:from scrapy_redis.spiders import RedisCrawlSpider

        • 注释掉allowed_domains = ['www.com.com'] start_urls = ['http://www.com.com/']

        • 添加一个新属性:redis_key=“zbb”可以被共享的调度器队列的名称

        • 编写数据解析相关的操作

        • 将当前爬虫类的父类修改成RedisCrawlSpider

      4. 修改配置文件settings

        1. 指定使用可以被共享的管道:

          ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 400 }

        2. 指定调度器:

          // 增加了一个去重容器类的配置, 作用使用Redis的set集合来存储请求的指纹数据, 从而实现请求 去重的持久化

          DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"

          // 使用scrapy-redis组件自己的调度器

          SCHEDULER = "scrapy_redis.scheduler.Scheduler"

          // 配置调度器是否要持久化, 也就是当爬虫结束了, 要不要清空Redis中请求队列和去重指纹的set。如果是True, 就表示要持久化存储, 就不清空数据, 否则清空数据

          SCHEDULER_PERSIST = True

      5. redis相关操作配置:

        • redis相关操作配置:

          • 配置redis的配置文件:

            • linux或者mac:redis.conf

            • windows:redis.windows.conf

            • 代开配置文件修改:

              • 将bind 127.0.0.1进行删除

              • 关闭保护模式:protected-mode yes改为no

          • 结合着配置文件开启redis服务

            • redis-server 配置文件

          • 启动客户端:

            • redis-cli

        • 执行工程:

          • scrapy runspider xxx.py

          • 向调度器的队列中放入一个起始的url:

            • 调度器的队列在redis的客户端中

          • 爬取到的数据存储在了redis的proName:items这个数据结构中

         

         

         

第九章:增量式爬虫

概念:

  • 监测网站数据更新的情况,只会爬取网站最新更新出来的数据。

案例:

  • 爬取80s电影网站的电影名称和简介:

  • 分析:

    • 指定一个起始的url

    • 基于CrawlSpider获取其他页码链接

    • 基于Rule对其他页码链接进行请求

    • 从每一个页码对应的页面源码中解析出每一部电影详情页的URL

    • 检测电影详情页的url有没有请求过(增量式核心)

      • 将爬取过的电影详情页的url存储

      • 存储到redis的set集合中

    • 对详情页的URL发起请求解析出电影名称和简介

    • 持久化存储

 

源码见码云:https://gitee.com/laoliNb666/python-spider_study/tree/master

posted @   还是啥都不会  阅读(230)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示