爬虫基础内容回顾

发布时间 2023-04-08 09:35:09作者: 凫弥
回顾基础内容
    浏览器.
        简单聊聊浏览器工作原理:
            浏览器在加载页面源代码的时候.
            会遇到一些特殊的东西
                1. 图片<img>
                2. css样式<link href="xxxxx.css">
                3. js文件<script src="xxxxx.js>
                4. js代码片段 <script>js代码</script>
                5. 各种其他信息的加载...
                上述内容. 都会触发一个新的网络请求...
                所以, 我们在抓包里. 可以看到如下情况
                1. douban.com
                2. xxxxx.css
                3. xxxxxx.js
                4. xxxxxxxxx.js
                5. xxxxxx.css
                6. xxxxx.jpg
                7. xxxxx.json  => 返回的数据是json数据
                8. xxxxxx => 返回的数据可能是json也可能不是json


        爬取一个网站应该从哪里入手?
        从页面源代码开始....
            1. 页面源代码里有你需要的东西
                直接发送请求到url上. 请求回来在正常情况下.
                一定可以获取到带有你需要数据的页面源代码

                直接拿, 直接干...
                如果拿到了. 直接解析
                如果拿不到. 需要添加请求头中的UA, COOKIE, REFERER
                    一定要去尝试....
                从今天开始. 你在发送请求的时候, 至少给个UA.
                这是你对网站最基本的尊重....

            2. 页面源代码里没有你需要的东西
                去抓包工具里找..找你需要的数据是从哪个请求里回来的...
                打开F12(devtools),在这里面可以看到一些重要的功能.

                1. (elements)当前页面节点结构
                    elements中的内容是你当前页面经过js的渲染, 经过页面源代码的渲染之后的实时状态.
                    它和页面源代码可能会有很大差别..

                    在你使用requests的时候. 你获取到的页面源代码. 而不是elements里的东西.
                    所以, 切忌, 不要用elements提供的xpath来处理你的页面解析(xpath)工作.

                    谁在出这个问题. 拉出去斩.斩立决...selenium除外....

                2. console 控制台.
                    负责展示js运行的结果.
                    用它来调试js代码

                3. source 资源
                    1. 可以看到当前浏览器中加载的所有静态资源
                    2. 可以编写代码
                    3. 可以对代码进行调试(debug)
                        逐行观察代码的运行情况.

                4. network 网络抓包
                    可以看到你浏览器加载该网页(网站)加载的所有网络请求
                    可以看到:
                        0. url以及http状态码, 请求方式(get, post)
                        1. 请求头
                        2. 响应头
                        3. 请求参数
                        4. 响应内容
                        5. 响应内容的预览...
                        6. 该请求发送之前. 执行的js函数的调用栈..帮助我们去溯源
                        7. cookie的使用情况(请求,响应)

                5. application 应用程序
                    放了一些存储相关的东西.
                    目前记住可以清理缓存和cookie信息即可

                6. search.
                    按下键盘的esc, 多按几次. 可以看到弹出窗口中有search.
                    或者点击浏览器devtools的右上角的三个点. 找到search也可以弹出该窗口
                    在这里可以对你需要的内容进行全文检索.

    发请求
        requests模块
            1. get请求
                requests.get()
            2. post请求
                requests.post()
            3. 关于参数的问题
                1. Query String Parameters
                    最终是要被添加在url上面的.
                    url = "http://wwww.baidu.com/s?name=周杰伦"

                    Query String Parameters:
                        name: 周杰伦

                    此时, 你可以有两个选择.
                    1. 把参数直接怼在url上面
                        url = https://movie.douban.com/j/chart/top_list?type=13&interval_id=100%3A90&action=&start=0&limit=20
                        requests.get(url)
                    2. 把参数弄出来. 弄成字典. 然后通过params传递给服务器
                        url = "https://movie.douban.com/j/chart/top_list"
                        params = {
                            type: 13
                            interval_id: 100:90
                            action:
                            start: 0
                            limit: 20
                        }

                        requests.get(url, params=params)
                    3. 还可以混合搭配.
                        url = "https://movie.douban.com/j/chart/top_list?type=13"
                        params = {
                            interval_id: 100:90
                            action:
                            start: 0
                            limit: 20
                        }

                        requests.get(url, params=params)

                    上述三种方案. 最终在服务器是一样的..原因是, 请求是要遵守http协议的
                    在http协议中. get请求的所有参数都要怼在url上面

                2. Form Data
                    首先Form Data一定是post请求.  早期是form表单提交的时候. 如果
                    有大量的数据就会用到form.
                    现在没那个说法了. 现在比较自由. 网站想怎么用就怎么用..

                    它的参数. 需要处理成字典. 然后再requests模块里.
                    url = "xxxxx"
                    data = {
                        数据
                    }

                    requests.post(url, data=data)

                3. Request Payload 表示的就是挂载.
                    挂载的数据类型不一定是json. 最多的是json...

                    在请求头里. 会有一个content-type属性. 来描述该挂载内容的格式

                    我们处理的时候. 一般是按照浏览器上面的提示. 来组装参数以及请求头

                    url = "xxxxx"
                    data = {
                       字典
                    }
                    解决方案:
                        1. 直接用json参数传递
                            requests.post(url, json=data)

                            把你传递的字典. 处理成json然后发送给服务器
                            隐藏的逻辑:
                                自动在请求头里帮你添加content-type: json....

                            上述逻辑是自动的

                        2. 手动把字典处理成json. 然后再请求头里增加content-type
                            把处理好的json用data传递出去
                            s = json.dumps(data, separators=(',', ':')) # json字符串

                            requests.post(url, data=s, headers={
                                "Content-Type": "application/json"
                            })

                            上述逻辑是自己手动来...
                4. 三种参数可以混搭....
                    Query String 和 Form Data
                    Query String 和 Requests payload

                    用上面的三种方案. 混合处理即可.

            4. 请求头.
                User-Agent: 表示的是你的网络设备...
                Referer: 防盗链.
                    a页面 发请求到b页面. 在b页面的请求头上面就会有referer, 是a页面的地址.
                Cookie:
                    客户端和服务器的会话状态.
                    应该如何处理cookie
                    1. 服务器在返回的http数据包的时候. 在响应头上会有Set-Cookie
                        Set-Cookie: xxxxx=xxxxx
                        告诉浏览器. 把该信息保存下来.以后使用我的网站的时候. 带着该cookie信息

                        保持cookie状态: session
                            session表示会话. 你再session中保存的请求头信息. cookie信息
                            可以重复使用...
                            可以帮我们保持住cookie的状态.

                            它只能帮你保持住, 响应头回来的cookie信息.

                    2. 在js中可以动态的修改cookie
                        有些网站不是通过`响应头`加载的cookie..... 这时候session就不行了....该信息是`js动态`加载的

                        此时. 我们只能想办法手工去维护cookie的改变策略. 还是要用session....

                    综上. 我们发请求的时候. 一定要用session

            5. 返回的数据,响应
                http状态码.
                    1. 10x
                        websocket
                    2. 200
                        服务器返回正常的内容. 没有问题
                    3. 30x 重定向, 重新发请求到某个url
                            到哪个url?

                            302 返回的时候. 会在响应头里 有一个location
                            location里放的就是重定向的地址.

                            一般我们不关心?? requests完成了像浏览器一样的重定向工作.
                            requests.get(baidu.com)
                            => 302  响应头:Location: google.com
                            => 浏览器会自动发送请求到google.com

                        有的网站用js来重定向....
                            <script>window.location.href= "http://www.baidu.com";</script>

                    4. 4xx
                        页面丢失, 意味着:
                            http://www.baidu.com/abc?xxxx
                            0. 服务器炸了.
                            1. 你给的url不对...
                            2. 故意不给你正确结果...反爬(大概率是`参数`不对)
                            3. 你的访问频率太高了...临时丢进小黑屋...
                    5. 5xx
                        服务器报错了...意味着: 500
                            1. 你给的参数不对..
                                json格式问题.... 新手容易出错的地方
                                看到的传递的参数是json格式(payload)
                                    url = "xxxxx"
                                    data = {
                                        type: 13,
                                        interval_id: 100
                                    }
                                    requests.post(url, data=data)
                                    浏览器上的参数:
                                        s = "{type: 13,interval_id: 100}"
                                        json.loads(s) 没问题的.

                                    如果此时你按照Form Data的方式传递
                                        s = "type=13&interval_id=100"
                                        json.loads(s) 必须爆炸.


                                网站是经过测试的....正常人的操作一般是不会出现错误的
                                出现了错误. 大概率你不是个正常人...

                            2. 服务器的程序员渣渣. 测试渣渣....

                接受返回的数据
                    resp.encoding="utf-8/gbk" | resp.encoding = resp.apparent_encoding

                    resp.text 先用它来确定返回的数据是什么. 然后再去决定后面的工作...

                    resp.json()  在没有确认服务器真的给`你`返回json之前. 不要用它...
                    
                    
                    
数据解析.
    1.针对字典(json), 自学模块. jsonpath
        python基础. 字典的数据提取, 列表的数据提取.
    2.针对html(xml)
        1. re 正则表达式.
            .*?  惰性匹配,尽可能少的去匹配内容
                我要玩游戏, 玩儿吃鸡游戏, 这个游戏特别好玩儿
                我要.*?游戏
            .*  贪婪匹配,尽可能多的去匹配内容
                我要玩游戏, 玩儿吃鸡游戏, 这个游戏特别好玩儿
                我要.*游戏
            \w  数字, 字母, 下划线  [a-zA-Z_]
            \d  匹配数字. [0-9]
            [字符组]

            量词
                {}
                +
                *
                ?

            import re

            # 预加载一个正则表达式
            obj = re.compile(r"我要(?P<qiaofu>.*?)游戏")
            obj.search("xxxx").group("qiaofu")
            obj.find()

            重点: 使用场景,
                如果你要的数据在html标签中....一般是不用正则的..
                需要在一个非HTML代码(js)中提取某一部分数据...

            re_str = r"window.__playinfo__(.*?)</script>"
            json.loads()

        2. bs4
            bs4.find("div", attrs={"id":"qiaofu"})
            bs4.find_all("div")
            bs4处理html还可以...没有xpath好用...处理xml很香....

        3. xpath
             对咱们来说就是处理html结构的.
             lxml =>
             1. 没代码提示.
             2. xpath()获取到的内容. 是列表

             from lxml import etree

             tree = etree.HTML(xxxx)

             div = tree.xpath("//div[@id='123456']")
             if div:
                div = div[0]
             else:
                记录当前url. 提示有问题....
                continue  # ???
             div.xpath(".//text()")

             容易犯错误:
                你拿到的页面源代码和你看到的页面源代码不一样.

数据存储.
    0. 文件夹
        os.path.exists(path)  判断文件路径是否存在
        os.makedirs(path) 创建多级目录
    1. 文本

        f = open("文件名", mode="w", encoding="utf-8")
        f.write("爬到的数据")

        如果所有的数据要保存在一个文件里面,
        千万不要把open写在循环里(while, for, 递归)

    2. csv
        这东西的本质是文本...txt没差别..
        很不幸. 他可以被excel打开. 别追求那个excel去.....

        写入csv用open...
        分隔符可以自定义. 但是要告诉客户.
        如果数据中有'逗号', 可以使用`双引号`括起来.

    3. xlsx
        用pandas转存csv即可.
        按照csv的逻辑写. 写完了之后. 用pandas转存即可.
        pip install pandas

        import pandas as pd

        df = pd.read_csv("data.csv", header=None)
        print(df)
        df.to_excel("qiaofu.xlsx")

        # openpyxl xlwt

    4. 数据库存储
        mysql.
            可能会用到的.
            插入数据.

            conn = None
            try:
                conn = pymysql.connect()
                cursor = conn.cursor()

                sql = insert into 表(字段1, 字段2, 字段3....) values(%s, %s, %s....)
                cursor.execute(sql, (数据1, 数据2, 数据3....))

                conn.commit()
            except Exception as e:
                # 记录日志..
                logging.log(traceback.exec_format())   # 记录报错信息
                # 把当前数据的url记录在另外的txt文件中...
                conn.rollback()
            finally:
                if conn:
                    conn.close()

        mongo. redis(程序设计)
    5. 字节文件
        open("xxxx.",mode="wb")

scrapy -> 让你看到一些优秀的设计...复杂的程序逻辑...
面向对象... scrapy源码(调度器)...

注意requests发请求所带的参数问题

dic = {
    "uname": "qia      ofu",
    "uage": 18,
    "hobby": ["刷牙", "洗脸"]
}

import json

# 这种方案. 直接转化成json. 会有空格存在....
s = json.dumps(dic, separators=(',', ':'))
print(s)

# {"uname":"qiaofu","uage":18,"hobby":["\u5237\u7259","\u6d17\u8138"]}
# {"uname": "qiaofu", "uage": 18, "hobby": ["刷牙", "洗脸"]}
# 有的网站专门检测这个空格....
# 下面是浏览器的....
# {"uname":"qiaofu","uage":18,"hobby":["刷牙","洗脸"]}
import requests

session = requests.session()

# 在session中保存了请求头
session.headers = {
    # "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36",
}

# 保存了cookie
session.cookies['abc'] = "modou"
session.cookies['def'] = "207"

#
# resp = session.get("https://www.baidu.com")
# print(resp.request.headers)
#
# # 执行一段js....会在cookie中加入一些新内容
# session.cookies['lucky'] = "qiao"
#
# # 后面该怎么弄就怎么弄....
resp = session.get("https://www.baidu.com")
print(resp.request.headers) # {'Cookie': 'abc=modou; def=207'}

resp = session.get("https://www.baidu.com")
print(resp.request.headers) # {'Cookie': 'abc=modou; def=207; BAIDUID=4794FBC9F5FDB937941317FEF6116E7F:FG=1; BIDUPSID=4794FBC9F5FDB937CBA03EEF276B6574; PSTM=1680915323; BD_NOT_HTTPS=1'}

resp = requests.get("http://www.baidu.com")
# resp = requests.get("http://www.baidu.com", allow_redirects=False)
resp.encoding=resp.apparent_encoding
print(resp.text)

写入CSV中

# lst = [
#     {"id": 1, "hobby": "吃惠灵'顿,牛排"},
#     {"id": 2, "hobby": "吃猪排"},
#     {"id": 3, "hobby": "吃羊排"},
#     {"id": 4, "hobby": "吃各种排"},
#     {"id": 5, "hobby": "吃排球"},
# ]
#
# f = open("data.csv", mode="w", encoding="utf-8")
# for item in lst:
#     id = item['id']
#     hobby = item['hobby']
#     print(id, hobby)
#     f.write(f"{id}_qiaofu_'{hobby}'\n")  # 告诉客户分隔符是什么..


# 这是你的客户.....
import pandas as pd

# 提示正在使用的CSV文件的分隔符参数不支持正则表达式,将会自动使用python引擎来执行CSV文件的读取操作。
df = pd.read_csv("data.csv", sep="_qiaofu_", header=None, engine="python")
print(df)

df.to_csv("xxxx.csv")
df.to_excel('xxxx.xlsx')
df.to_markdown('xxxx.md')
df.to_html('xxxx.html')

面向对象的思维

# 面向过程: 一切以流程为核心. 爬虫..最多就是个函数式编程...
# 面向对象: 一切以对象为核心..
# 你需要做的任何事情都交给对象来完成.
# 怎么造对象...
# 规划对象....
# 在程序里. 需要先写类. 类是描述对象的东西.
# 你想要对象干什么. 在类里面就写什么.

# # 一类神奇的东西.
# # list 类, str 类, dict 类
# class NvPengYou:
#     def nakele(self):
#         print("女朋友帮我拿可乐去")
#         return "可乐"
#     def zuofan(self):
#         print("会做饭")
#
#
# # # 造对象,  用类去创建对象
# p1 = NvPengYou()  # 真的按照这个标准, 找到了一个女朋友
# # # ret = p1.nakele()
# # # print(ret)
# #
# # p2 = NvPengYou()  # 按照这个标准, 又找了一个..
# #
# # # 没关系: 完全独立的两个人... 不同的内存地址...
# # # 林志玲, 仓木麻衣.
# #
# # # 有关系: 他们两个都是按照我的要求找的女朋友... 他们能做的事儿. 是相同的.
# # # 属性关系应该是相同...
# # p1.nakele()
# # p2.nakele()
#
# p = NvPengYou()
# p.zuofan()   # 报错. 在类中规划对象的时候. 就没有这个功能
# # 对象能做什么. 取决于. 类里面有什么...


# s = "我爱你"  # 对象....
# s.strip()
# print(dir(str))
#
# # 类: 类型....

class Person:
    def chi(self):
        pass

    def nakele(self):
        pass


class NvPengYou(Person):

    def __init__(self, name, age, height, weight, address):  # 特殊方法. init会在创建对象的时候. 自动执行
        self.name = name
        self.age = age
        self.height = height
        self.weight = weight
        self.address = address

    def nakele(self):  # 方法...
        # self => 当前正在执行该方法的那个对象(随动)
        # self 就是 当前对象
        print(self.name, "帮我那可乐")
        pass

# 对象是需要自己的信息(属性)的...
p1 = NvPengYou("林志玲", 18, 180, 100, "沙河")
p2 = NvPengYou("马冬梅", 26, 160, 120, "海淀")

p1.nakele()
p2.nakele()