爬虫学习笔记(入门)

参考视频

基本能力(必看)

  • 会使用浏览器上网,建议浏览器Google , Edge。(不建议国内浏览器)

  • 会检查网页代码, 抓包分析, js逆向等。

  • 一定的python,HTML基础

  • 小白请使用参考视屏辅助学习

  • 本人也是小白一枚

http复习

http与https的概念与区别

https比http更安全但性能更低

常见的请求头与响应头

请求头:
host:域名
Connection:长连接
Upgrade-Insecure-Requests:升级为https协议
* User-Agent:用户代理,提供系统浏览器信息
* Referer:页面跳转处(从哪来),防盗链
* Cookie:状态保持

响应头:
set-cookie:设置HTTP cookie

注意以上仅是常用请求头,其余请参见: 请求头与响应头

状态码

分类:
1**	信息,服务器收到请求,需要请求者继续执行操作
2**	成功,操作被成功接收并处理
3**	重定向,需要进一步的操作以完成请求
4**	客户端错误,请求包含语法错误或无法完成请求
5**	服务器错误,服务器在处理请求的过程中发生了错误

常见状态码:
200 - 请求成功
301 - 资源(网页等)被永久转移到其它URL
404 - 请求的资源(网页等)不存在
500 - 内部服务器错误

详情参见:状态码

注意所有状态码都不可信,一切以从network中抓包分析为准(防反爬)

浏览器的请求过程

浏览器:发送请求,进行渲染
爬虫:只发送请求,不渲染
渲染流程:html静态文件 --> js/ajax请求 --> css/font/图片

注意以上渲染的流程也是抓包分析的流程

requests模块

简介

  • 官方文档

  • 作用:发送请求获取响应,替代模块urllib,但requests模块更简洁明了。

  • 发送get请求

import requests

# 目标url
url = 'https://www.baidu.com'

# 向目标url发送get请求
response = requests.get(url)

# 打印响应内容
print(response.text)

response响应对象

  • response.text是requests模块按照chardet模块推测出的编码字符集进行解码的结果。

  • 因此可以手动设置解码格式

# 设置解码格式
response.encoding = 'utf-8'

# 打印响应内容
print(response.text)
  • 网络传输的字符串都是bytes类型的,所以可以对字节流进行解码
# 或者对字节流进行解码
print(response.content.decode('utf-8'))

总结

  • response.text:类型是str,已经做了相应解码。

  • response.content:类型是bytes,没有指定解码类型,可以自由设置解码类型。

  • 常见编码字符集:utf-8, gbk, gb2312, ascii, iso-8859-1

response响应对象的其他方法

# 响应的url,有时响应的url与请求的url并不一致
print(response.url)

# 响应状态码
print(response.status_code)

# 响应对应的请求头
print(response.request.headers)

# 响应头
print(response.headers)

# 响应对应的请求的cookie,返回cookieJar类型
print(response.request._cookies)

# 响应的cookie(经过了set-cookie动作后)
print(response.cookies)

# 自动将json字符串类型的响应内容转为python对象
print(response.json())

# 返回结果
# https://www.baidu.com/
# 200
# {'User-Agent': 'python-requests/2.26.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}
# {'Cache-Control': 'private, no-cache, no-store, proxy-revalidate, no-transform', 'Connection': 'keep-alive', 'Content-Encoding': 'gzip', 'Content-Type': 'text/html', 'Date': 'Fri, 08 Oct 2021 03:54:39 GMT', 'Last-Modified': 'Mon, 23 Jan 2017 13:23:55 GMT', 'Pragma': 'no-cache', 'Server': 'bfe/1.0.8.18', 'Set-Cookie': 'BDORZ=27315; max-age=86400; domain=.baidu.com; path=/', 'Transfer-Encoding': 'chunked'}
# <RequestsCookieJar[]>
# <RequestsCookieJar[<Cookie BDORZ=27315 for .baidu.com/>]>

requests模块发送get请求(带参数)

发送带请求头的参数

import requests


# 目标url
url = 'https://www.baidu.com'

# 请求头, 模拟浏览器发送请求
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)"
                  " Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"
}

# 向目标url发送get请求
response = requests.get(url, headers=headers)

print(response.content.decode())

发送带参数的请求

  • 直接对含有参数的url发起请求
# 直接对含有参数的url发起请求
import requests


url = "https://www.baidu.com/s?wd=python"

response = requests.get(url, headers=headers)

print(response.content.decode())
  • 建立参数字典
# 建立参数字典
import requests


url = "https://www.baidu.com/s?"

# 请求头, 模拟浏览器发送请求
headers = {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application"
              "/signed-exchange;v=b3;q=0.9",
    "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)"
                  " Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"
}

params = {
    "wd": "python"
}

response = requests.get(url, headers=headers, params=params)

print(response.content.decode())

注意如果出现百度安全验证,则在请求头中加入"Accept",“Accept-Language”

cookie参数

网站经常利用请求头中的cookie字段来做用户访问状态的保持,因此我们可以在headers参数中添加cookie,来模拟普通用户的请求

import requests

url = 'https://www.acwing.com/problem/'

# 请求头, 模拟浏览器发送请求
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)"
                  " Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67",
    "Cookie": "csrftoken=b3jj8qGGw9RRE6E6VhhWNm1KqbRM7lWKW4wWJEP6QbZfvEEFMrbLqCf8JUSf7YoG; sessionid=d3w2dub0yas41kevn"
              "ihdm6qoib28q939"
}

response = requests.get(url, headers=headers)

print(response.content.decode())

# 出现个人信息:说明模拟登陆成功(大约在390-400行出现)
# <strong id="id_user_username">陆依依</strong>

注意

  • 上述cookie值中包含本人个人信息,请慎用!!!

  • cookie一般有过期时间,需要重新获取

  • 也可以在get请求中加入cookies参数,如下:

import requests


# 使用cookie参数
url = 'https://www.acwing.com/problem/'

# 请求头, 模拟浏览器发送请求
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)"
                  " Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"
}

cookies = "csrftoken=b3jj8qGGw9RRE6E6VhhWNm1KqbRM7lWKW4wWJEP6QbZfvEEFMrbLqCf8JUSf7YoG; sessionid=d3w2dub0yas41kevn" \
          "ihdm6qoib28q939"

# 加工cookies字典
cookies = {cookie.split("=")[0]: cookie.split("=")[-1] for cookie in cookies.split(";")}

response = requests.get(url, headers=headers, cookies=cookies)

print(response.content.decode())

cookieJar对象转换为cookie字典

response.cookies是一个cookieJar对象,包含了对方服务器设置在本地的cookie

# 获取的cookies属性
print(response.cookies)

# 转为字典
cookies_dict = requests.utils.dict_from_cookiejar(response.cookies)
print(cookies_dict)

# 转为cookieJar对象
cookies_jar = requests.utils.cookiejar_from_dict(cookies_dict)
print(cookies_jar)

# # 运行结果:
# <RequestsCookieJar[<Cookie csrftoken=b3jj8qGGw9RRE6E6VhhWNm1KqbRM7lWKW4wWJEP6QbZfvEEFMrbLqCf8JUSf7YoG for www.acwing.com/>]>
# {'csrftoken': 'b3jj8qGGw9RRE6E6VhhWNm1KqbRM7lWKW4wWJEP6QbZfvEEFMrbLqCf8JUSf7YoG'}
# <RequestsCookieJar[<Cookie csrftoken=b3jj8qGGw9RRE6E6VhhWNm1KqbRM7lWKW4wWJEP6QbZfvEEFMrbLqCf8JUSf7YoG for />]>

超时参数timeout的使用

在网络请求中,可能会遇到网络延迟,造成项目效率降低,timeout可以使程序在规定时间内必须返回结果,否则报错!(抛出异常)

import requests


url = ''

# 3秒内没有响应会抛出异常
requests.get(url, timeout=3)

# 运行结果:
# requests.exceptions.MissingSchema: Invalid URL '': No schema supplied. Perhaps you meant http://?

代理proxy

- 理解使用代理的过程

1、代理IP是一个IP,指向的是一个代理服务器

2、代理服务器能够帮我们找到目标服务器并转发请求

- 正向代理和反向代理

1、正向代理:为浏览器或客户端(发送请求的一方)转发请求,如VPN

2.反向代理:为最终处理请求的服务器转发请求,如nginx

3、正向代理知道目标服务器,而反向代理不知道

4、proxy参数指定的代理IP指向的是正向代理服务器

- 代理的分类

根据IP划分

透明代理:使用了透明代理去访问目标服务器,对方服务器会知道你正在使用代理,并且能识别你本地真实ip地址
透明代理访问目标服务器的所带的HTTP头信息如下:
REMOTE_ADDR = 代理服务器IP
HTTP_VIA = 代理服务器IP
HTTP_X_FORWARDED_FOR = 你的真实IP
透明代理无法隐藏真实ip地址,因此无法突破目标服务器的限制。

匿名代理:使用了匿名代理去访问目标服务器,对方服务器会知道你正在使用代理,但是无法识别你的真实IP
匿名代理访问目标服务器所带的HTTP头信息如下:
REMOTE_ADDR = 代理服务器IP
HTTP_VIA = 代理服务器IP
HTTP_X_FORWARDED_FOR = 代理服务器IP

高匿名代理:使用高匿名代理目标服务器不仅无法识别你的真实ip,还无法识别你使用了代理。
高匿名代理访问对方服务器所带的HTTP头信息如下:
REMOTE_ADDR = 代理服务器IP
HTTP_VIA 不显示
HTTP_X_FORWARDED_FOR 不显示

高匿名代理很好的隐藏本地ip,使用伪装的ip去访问目标网站,从而突破网站的ip限制。
所以在选择代理ip的时候一定要选择像万变ip这样的代理,每日上百万动态高匿名ip。
只有高匿名的ip才能突破各种ip限制。

根据协议划分

HTTP代理:能够代理客户机的HTTP访问,主要是代理浏览器访问网页,它的端口一般为80、8080、3128等。

FTP代理:能够代理客户机上的FTP软件访问FTP服务器,它的端口一般为21、2121。

RTSP代理:代理客户机上的Realplayer访问Real流媒体服务器的代理,其端口一般为554。

POP3代理:代理客户机上的邮件软件用POP3方式收发邮件,端口一般为110。

SOCKS代理:SOCKS代理与其他类型的代理不同,它只是简单地传递数据包,而并不关心是何种应用协议,所以SOCKS代理服务器比其他类型的代理服务器速度要快得多。
SOCKS代理又分为SOCKS4和SOCKS5,二者不同的是SOCKS4代理只支持TCP协议(即传输控制协议),而SOCKS5代理则既支持TCP协议又支持UDP协议(即用户数据包协议),还支持各种身份验证机制、服务器端域名解析等。
SOCK4能做到的SOCKS5都可得到,但SOCKS5能够做到的SOCKS则不一定能做到,比如我们常用的聊天工具QQ在使用代理时就要求用SOCKS5代理,因为它需要使用UDP协议来传输数据。

- 使用代理

import requests


# 目标url
url = 'https://www.baidu.com'

proxies = {
    "http": "http://112.6.117.178:8085"
}

# 请求头, 模拟浏览器发送请求
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)"
                  " Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"
}

# 向目标url发送get请求
response = requests.get(url, headers=headers, proxies=proxies)

print(response.content.decode())

注意

  • 寻找代理IP

  • 代理IP尽量使用http类型,并加上headers头,否则容易报错

CA证书

有些网站没有CA证书,访问会失败,需要加一个参数:verify=False来忽略CA认证

response = requests.get(url, verify=False)

requests模块发送post请求

- post更安全且没有数据长度限制

- post请求相对于get请求多了一个data参数,data是一个字典存放表单参数

- 下面以md5加密为例,演示post请求的用法(适当添加面向对象的思想)

# json.loads()可以将str类型转为json对象
import json       
import requests


class Md5(object):

    def __init__(self, message):
        self.url = 'https://www.matools.com/md5'
        self.data = {
            "str": message
        }

    def run(self):
        response = requests.post(self.url, data=self.data)

        print(json.loads(response.content.decode())["upper32"])
        print(json.loads(response.content.decode())["lower32"])
        print(json.loads(response.content.decode())["upper16"])
        print(json.loads(response.content.decode())["lower16"])


if __name__ == '__main__':
    message = "网络空间安全"
    md5 = Md5(message)
    md5.run()

# 运行结果:
# 32位大写:D58389AB3D32FD387335B2B82AC8D0DB
# 32位小写:d58389ab3d32fd387335b2b82ac8d0db
# 16位大写:3D32FD387335B2B8
# 16位小写:3d32fd387335b2b8

总结:post中data参数数据来源:

1. 固定值       抓包比较不变值

2. 输入值       自己手动输入   

3. 预设值       需要提前在html文件中获取,或者向指定url发送请求

4. 在客户端产生  模拟js,产生数据

补充知识:时间戳

 定义:时间戳是指格林威治时间自1970年1月1日(00:00:00 GMT)至当前时间的总秒数。

 获取方式:	先 import time 然后 time.time()

 例子:1633758382.6187418

时间戳详情参考

利用requests.session进行状态保持

  • requests中的session类能够自动处理发送请求获取响应过程中产生的cookie,进而达到状态保持的目的。

  • session实例在请求一个网站后,对方服务器设置在本地的cookie会保持在session中,下一次在使用session请求对方服务器时,会带上上一次的cookie。

  • 一般用于模拟登录场景。

# 学习通中的密码仅仅采用base64编码,并未加密
# 登录过程中,还用到了时间戳,这里不再展开
import base64
import requests


class XueXiTong(object):

    def __init__(self, username, password):
        self.url = "https://passport2.chaoxing.com/fanyalogin"
        self.session = requests.session()
        self.data = {
                "uname": username,
                "password": base64.b64encode(password.encode()),
                "t": "true",
                "forbidotherlogin": "0",
                "fid": "-1",
                "refer": "http%3A%2F%2Fi.chaoxing.com"
        }

    def run(self):
        response = self.session.post(self.url, data=self.data)
        print(response.content.decode())

        # 带上上一次的cookie后再次发送请求,下面已经会出现个人信息,说明登陆成功
        res = self.session.get('http://i.chaoxing.com')
        print(res.content.decode())


if __name__ == '__main__':
    username = '用户名'     # 填写自己的用户名(登陆时使用的)
    password = '密码'       # 填写自己的密码
    xuexitong = XueXiTong(username, password)
    xuexitong.run()

数据提取

数据分类

结构化数据:
    json数据(高频):处理方法有json模块,re模块,jsonpath模块
    xml数据(低频):处理方法有re模块,lxml模块
非结构化数据:
    html:处理方法有re模块,lxml模块,beautifulSoup模块等

xml:可扩展标记语言,用于数据的传输
html:超文本标记语言,用于数据的显示

jsonpath模块

  • 作用:直接提取多层字典中的数据

  • jsonpath语法规则

$:     根节点
.       子节点(相邻层级)
..      任意子节点(可跳跃层级)

jsonpath语法使用手册参考

import json
import jsonpath
import requests

url = "https://www.lagou.com/lbs/getAllCitySearchLabels.json"

# 请求头, 模拟浏览器发送请求
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)"
                  " Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"
}

response = requests.get(url, headers=headers)

# 全部数据
print(json.loads(response.content))

# A开头的城市
print(jsonpath.jsonpath(json.loads(response.content), '$..A..name'))

# 全部城市
print(jsonpath.jsonpath(json.loads(response.content), '$..name'))

lxml模块

/                               绝对路径
//                              相对路径
.                               当前层节点
..                              上一层节点
/text()                         取出标签中的文本
/@属性名                        取出属性值
/div[i]                         选中当前层的第i个div
/div[last()]                    选中当前层最后一个div
/div[last()-1]                  选中当前层倒数第2个div
div[position()>5]               选中第5个位置后的div
/div[@id='1']                   选中id为1的div
/div[contains(@id, '1')]        选中id包含1的div
/div[span='2']                  选中div中span值为'2'的div
/div[contains(text(), '南京')]   选中文本中包含'南京'的div
*                               通配符
|                               或
  • 小练习1(爬取信大软院新闻)
import requests
from lxml import etree

url = 'https://scs.nuist.edu.cn'

response = requests.get(url)

html = etree.HTML(response.content)

# 取出南信大计软院的主页中包含"国庆"的链接的标题
print(html.xpath("//a[contains(text(), '国庆')]/text()")[0])

# 取出南信大计软院的主页中包含"国庆"的链接的url地址, 需要简单的拼接一下
print(url + html.xpath("//a[contains(text(), '国庆')]/@href")[0])

# 运行结果:
# 国庆┃筑梦灯火万家,同庆盛世繁华,热烈庆祝中华人民共和国成立72周...
# https://scs.nuist.edu.cn//2021/0930/c5922a183248/page.htm
  • 小练习2(爬取信大软院新闻)
import requests
from lxml import etree


class Ad(object):
    def __init__(self, name):
        self.url = 'https://scs.nuist.edu.cn/{}/list.htm'.format(name)

    def getData(self, url):
        return requests.get(url).content

    # 解析数据
    def parseData(self, data):
        html = etree.HTML(data)
        el_list = html.xpath('//li/div/a')

        data_list = []

        for el in el_list:
            temp = {}
            temp['标题'] = el.xpath('./@title')[0]
            temp['链接'] = 'https://scs.nuist.edu.cn' + el.xpath('./@href')[0]
            data_list.append(temp)

        # 获取下一页url, 有可能没有下一页
        try:
            next_url = 'https://scs.nuist.edu.cn' + html.xpath('//a[3]/@href')[0]
        except:
            next_url = None

        return data_list, next_url

    def saveData(self, data_list):
        for data in data_list:
            print(data)

    def run(self):
        next_url = self.url
        while True:
            # 有可能下一页的url不能访问
            try:
                data = self.getData(next_url)
            except:
                break
            data_list, next_url = self.parseData(data)
            self.saveData(data_list)
            if next_url == None:
                break


if __name__ == '__main__':
    # 'tzgg':通知公告, 'ktdt':科研动态, 'jwxx':教务信息, 'xzfc':学子风采
    ad = Ad('xzfc')
    ad.run()
  • etree.HTML(str)能够自动补全缺失的标签,etree.tostring(html)可以再转换为html格式

Selenium自动化测试框架

selenium基础

  • selenium能够大幅度降低爬虫的编写难度,但同时会大幅降低爬虫的爬取速度,非必要不使用

  • selenium是一个Web的自动化测试工具,最初是为了网站自动化测试而开发

  • selenium可以直接调用浏览器,支持所有主流浏览器(包括PhantomJS等无界面浏览器)

  • selenium可以接受指令,让浏览器自动加载界面,获取需要的数据,甚至页面截屏等

  • 使用谷歌浏览器需要安装chromedriver驱动,一定要与谷歌浏览器的版本对应
    下载地址
    安装教程

  • 无界面浏览器phantomjs的安装与使用

  • example:建议使用谷歌浏览器,其余浏览器自行选择,(360,qq等浏览器请绕行)

from selenium import webdriver


# 有界面的谷歌浏览器,配置环境变量(重启计算机)后可不添加参数
driver = webdriver.Chrome()
# 无界面的phantomjs浏览器,该浏览器已被selenium弃用,建议使用谷歌火狐等浏览器
# driver = webdriver.PhantomJS()
# 其余浏览器,以Edge浏览器为例,如果驱动路径已添加至环境变量,可不加参数
# driver = webdriver.Edge(executable_path="驱动路径")

# 发送请求
driver.get('https://scs.nuist.edu.cn/')

# 保存页面截图
driver.save_screenshot('01.png')

# 打印页面的标题
print(driver.title)

# 退出关闭进程,也可以手动关闭
driver.quit()
  • 说明:如果仅是业余爬虫爱好者,使用chrome浏览器即可,但若将项目部署在服务器上时,则必须使用无界面浏览器

selenium的工作原理

  • 安装driver驱动是控制浏览器的必要条件,不同浏览器因为厂商不同需要安装不同的driver驱动

  • 流程:python代码–>调用不同浏览器的webdriver–>控制浏览器实现相应功能

  • example:

from selenium import webdriver
import time


# 实例化一个浏览器对象
driver = webdriver.Chrome()

# 控制浏览器访问一个url
driver.get('https://www.baidu.com/')
# 休眠3秒
time.sleep(3)

# 在浏览器搜索框中搜索南信大
driver.find_element_by_id('kw').send_keys('南信大')
time.sleep(2)

# 点击百度搜索
driver.find_element_by_id('su').click()

time.sleep(6)
# 休眠6秒后退出浏览器
driver.quit()

selenium的使用

  • driver对象的常用属性和方法
driver.page_source:                 显示当前标签页渲染过后的源码(省略了抓包分析的过程,所以使用极其简单)
driver.current_url:                显示当前标签页的url
driver.title:                      显示当前标签页的标题
driver.forward():                  前进
driver.back():                     后退
driver.close():                    关闭页面
driver.quit():                     退出浏览器
driver.save_screenshot(filename):  网页截图

上述常用方法使用极其简单,当前不再一一验证,后续会组合使用
截图使用场景之一:截取验证码,并识别
  • 定位标签元素的方法
定位一个元素	                        	定位多个元素	                        含义
find_element_by_id()	                find_elements_by_id()	                通过元素id定位
find_element_by_name()	                find_elements_by_name()	                通过元素name定位
find_element_by_xpath()	                find_elements_by_xpath()	        	通过xpath表达式定位
find_element_by_link_text()	        	find_elements_by_link_text()	        通过完整超链接定位
find_element_by_partial_link_text()		find_elements_by_partial_link_text()	通过部分链接定位
find_element_by_tag_name()	        	find_elements_by_tag_name()	        	通过标签定位
find_element_by_class_name()	        find_elements_by_class_name()	        通过类名进行定位
find_elements_by_css_selector()	        find_elements_by_css_selector()         通过CSS选择器定位标签

上述常用方法使用极其简单,当前不再一一验证,后续会组合使用
  • 标签对象提取文本内容和属性值
element.text                    获取文本内容
element.get_attribute('href')   获取属性值
element.click()                 点击
element.send_keys()             发送信息到输入框
element.clear()                 清空输入框
  • 综合实例之信大公告
from selenium import webdriver

driver = webdriver.Chrome()

driver.get('https://bulletin.nuist.edu.cn/791/list.htm')

with open('信息公告.csv', 'w', encoding='utf-8') as f:
    f.write('标题,发布日期,链接\n')

# 获取南信大前三页的信息公告标题,发表日期,链接
for page in range(3):
    print('*'*10 + '正在获取第{}页内容...'.format(page+1))
    # 定位标签
    ret1 = driver.find_elements_by_xpath('//*[@id="container"]/div/div[2]/div[2]/div//li/span[1]/span[4]/a')
    ret2 = driver.find_elements_by_xpath('//*[@id="container"]/div/div[2]/div[2]/div//li/span[3]/span/a')

    # 提取内容
    for i, j in zip(ret1, ret2):
        # print('标题:', i.text, '发布日期:', j.text, '链接:', i.get_attribute('href'))
        with open('信息公告.csv', 'a', encoding='utf-8') as f:
            f.write(i.text + ',' + j.text + ',' + i.get_attribute('href') + '\n')

    # 点击下一页
    driver.find_element_by_class_name('next').click()

driver.quit()

selenium的其它使用

标签页的切换

  • 当selenium控制浏览器打开多个标签页时,有时浏览器发生了跳转,但selenium却没有跳转(小坑,写爬虫时需要注意)

  • 切换步骤:

    • 获取所有标签页的窗口句柄

    • 利用窗口句柄切换到句柄指向的标签页

  • example:

from selenium import webdriver

url = 'https://nj.58.com/'

driver = webdriver.Chrome()

driver.get(url)

# 打印当前页面的url和窗口句柄
print(driver.current_url)
print(driver.window_handles)

# 定位并点击租房按钮
driver.find_element_by_xpath('/html/body/div[3]/div[1]/div[1]/div/div[1]/div[1]/span[1]/a').click()

# 再次打印当前页面url发现并未跳转,但窗口句柄发生了变化
print(driver.current_url)
print(driver.window_handles)

# 切换窗口句柄,新打开的窗口一般再句柄列表的最后
driver.switch_to.window(driver.window_handles[-1])

# 再次检查当前页面的url
print(driver.current_url)

rets = driver.find_elements_by_xpath('//div[6]/div[2]/ul/li/div[2]/h2/a')

for ret in rets:
    print(ret.text, ret.get_attribute('href'))

driver.quit()

frame小窗口的切换

  • frame窗口内的标签并不能在页面文件中直接定位,需要先切换至该frame窗口下,再进行定位

  • 以登录QQ空间为例:

import time
from selenium import webdriver

url = 'https://qzone.qq.com/'

driver = webdriver.Chrome()

driver.get(url)

# 通过id定位frame
# driver.switch_to.frame('login_frame')

# 也可以通过XPath路径定位
path = driver.find_element_by_xpath('//*[@id="login_frame"]')
driver.switch_to.frame(path)

driver.find_element_by_id('switcher_plogin').click()
driver.find_element_by_id('u').send_keys('QQ号')        # 输入自己的QQ号
driver.find_element_by_id('p').send_keys('QQ密码')      # 输入自己的QQ密码
driver.find_element_by_id('login_button').click()

time.sleep(10)
driver.quit()

获取cookie值

获取到的cookie可以配合requests模块使用,提高性能

from selenium import webdriver


url = 'https://www.baidu.com'

driver = webdriver.Chrome()

driver.get(url)

# 获取所有的cookie
print(driver.get_cookies())
# 删除多余的信息,并转换为字典
cookies = {data['name']: data['value'] for data in driver.get_cookies()}

print(cookies)

driver.quit()

执行js代码

有时浏览器并不能直接执行点击操作,需要下拉滚动条,此时需要执行js代码

小练习:点击51job主页下面的 “关于我们” 的链接

import time
from selenium import webdriver

driver = webdriver.Chrome()

driver.get('https://www.51job.com/')

#                  (水平滚动的距离, 竖直滚动的距离)
js = 'window.scrollTo(0, document.body.scrollHeight)'

# 执行js代码
driver.execute_script(js)

time.sleep(2)
driver.find_element_by_xpath('//div[8]//div[2]/a[1]').click()

time.sleep(3)
driver.quit()

页面等待

  • 强制等待

    • 使用方法:time.sleep()

    • 缺点:不够灵活

  • 隐式等待

    • 说明:针对元素定位,设置了一个等待时间,在该时间内定位成功执行下一步,否则报错超时加载

    • 使用方法:driver.implicitly_wait(10)

    • 只需设置一次

  • 显式等待

    • 每隔一段时间就停止检查条件,达成则停止等待,否则继续等待直至超时

    • 不是很重要,而且相对比较复杂,了解即可

    • 了解详情

开启无界面模式

  • 无界面浏览器与有界面浏览器存在一定差别,使用时可能会遭到拦截。

  • 但服务器大多是无界面的,所以使用有界面浏览器的无界面模式显得十分有必要

  • example:

from selenium import webdriver

# 创建配置对象
opt = webdriver.ChromeOptions()

# 添加配置参数
opt.add_argument('--headless')      # 开启无头模式
opt.add_argument('--disable-gpu')   # 不使用gpu

# 实例化浏览器对象时添加配置对象
driver = webdriver.Chrome(options=opt)

driver.get('https://www.nuist.edu.cn/')

driver.save_screenshot('信大主页.png')

ip代理

  • opt.add_argument(’–proxy-server=代理ip’)

  • 使用方法参考无头模式,不再演示

更换user-agent

  • opt.add_argument(’–user-agent=???’)

  • 使用方法参考无头模式,不再演示

综合实例之爬取京东商品信息

  • 理论上下列程序可以爬取任意商品信息

  • 但商品名称过多,本人难以一一验证,所以不能保证其普适的正确性

  • 如部分商品爬取出现错误,请根据实际情况手动改动即可

  • 本人已验证手机,电脑,键盘,鼠标等商品,其余商品待测

import time
from selenium import webdriver


class JD(object):
    def __init__(self, message):
        self.url = 'https://www.jd.com/'
        # 创建配置对象
        opt = webdriver.ChromeOptions()
        # 添加配置参数
        opt.add_argument('--headless')  # 开启无头模式
        opt.add_argument('--disable-gpu')  # 不使用gpu
        self.driver = webdriver.Chrome(options=opt)
        self.message = message
        # 隐式等待,建议加上,否则可能由于加载原因导致定位不到元素
        self.driver.implicitly_wait(10)
        with open('京东{}商品信息.csv'.format(message), 'w', encoding='utf-8') as f:
            f.write('{}信息介绍, 价格, 链接\n'.format(message))

    def find(self):
        self.driver.get(self.url)
        self.driver.find_element_by_xpath('//*[@id="key"]').send_keys(self.message)
        self.driver.find_element_by_xpath('//*[@id="search"]/div/div[2]/button').click()

    def getData(self):
        # 每隔0.2秒下拉滚动条,进行动态加载
        for i in range(20):
            self.driver.execute_script('window.scrollBy(0, 500)')
            time.sleep(0.2)
        # 简介
        data1 = self.driver.find_elements_by_xpath('//*[@id="J_goodsList"]/ul/li//a/em')
        # 价格
        data2 = self.driver.find_elements_by_xpath('//*[@id="J_goodsList"]//li/div//div[@class="p-price"]')
        # 商品链接
        data3 = self.driver.find_elements_by_xpath('//*[@id="J_goodsList"]/ul/li//div[@class="p-img"]/a')

        return data1, data2, data3,

    def saveData(self, data1, data2, data3):
        with open('京东{}商品信息.csv'.format(self.message), 'a', encoding='utf-8') as f:
            for i, j, k in zip(data1, data2, data3):
                # 每一页商品列表有60项,但少部分商品有多选项(这里忽略)会导致出现空值
                if len(data1) > 60 and i.text.replace(' ', '').replace('\n', '') == '':
                    continue
                f.write('{},{},{}\n'.format(i.text.replace('\n', ' '), j.text.replace('\n', ''), k.get_attribute('href')))

    def run(self):
        self.find()
        i = 1
        while True:
            print('正在爬取第{}页商品信息...'.format(i))
            data1, data2, data3 = self.getData()
            self.saveData(data1, data2, data3)
            try:
                self.driver.find_element_by_xpath('//*[@id="J_bottomPage"]/span[1]/a[@class="pn-next"]').click()
                i = i + 1
            except:
                break

        self.driver.quit()


if __name__ == '__main__':
    message = '手机'        # 输入商品名称
    jd = JD(message)
    jd.run()

反爬虫

反爬原因

  • 流量压力,会给对方服务器造成巨大压力,严重者会影响正常用户体验

  • 竞争压力,恶意竞争

  • 法律压力,法律未明令禁止,不受网络安全法约束

反爬对象

  • 学生党的低级小爬虫

  • 初创公司的低级小爬虫

  • 托管在服务器上的无主爬虫

  • 竞争对手的所有爬虫

  • 失控的搜索引擎

目标:禁止一切爬虫

反爬方向

基于身份识别

  • 通过headers字段来反爬

    • User-Agent

      反爬原理:爬虫默认情况下没有User-Agent,而是使用模块默认设置
      解决方法:添加上即可或者使用User-Agent池
      
    • referer

      反爬原理:爬虫默认情况下没有referer字段,服务器可以判断请求发起的源头
      解决方法:添加上即可
      
    • cookie

      反爬原理:通过检查cookies来查看发起请求的用户是否具有相应权限
      解决方法:进行模拟登录,成功获取cookies之后再进行数据的爬取
      
  • 通过请求参数来反爬

    服务器可以通过检查请求参数是否正确来判断是否为爬虫

    • 从静态文件中获取请求数据

    • 通过发送请求获取请求数据

    • 通过js生成请求参数

    • 通过验证码来反爬

基于爬虫行为

  • 基于请求频率或总请求数量(爬虫远高于普通用户)

    • 通过请求ip/账号单位时间内总请求数量
    反爬原理:正常浏览器请求网站,速度不会太快
    解决方法:购买ip或者账号(确保自己ip和账号的安全)
    
    • 通过同一ip/账号请求之间的间隔
    反爬原理:正常人使用浏览器请求网站,时间间隔是随机的,而程序间隔一般是固定的且较短
    解决方法:添加随机间隔,但会降低性能,必要时可以使用代理池或者多个账号
    
    • 通过请求ip/账号每天请求次数设置阈值
    反爬原理:正常的浏览行为,一天的浏览次数是有限的,超过阈值服务器会拒绝响应
    解决方法:多ip/账号,请求时间随机休眠
    
  • 基于爬虫行为(步骤)

    • 通过js实现跳转
    反爬原理:js实现页面跳转,无法在源码中获取下一页url
    解决方法:多次抓包,找到url之间的规律
    
    • 通过设置陷阱
    反爬原理:设置标签的不可见属性,正常人不会浏览,而爬虫会中计
    解决方法:找出陷阱,做出相应规避
    
    • 通过假数据
    反爬原理:添加假数据污染数据库,正常人无法看到假数据
    解决方法:长期运行,分析数据
    
    • 阻塞任务队列
    反爬原理:生成大量的垃圾url,浪费爬虫时间
    解决方法:过滤垃圾url
    
    • 阻塞网络IO
    反爬原理:发送请求获取响应的过程就是下载的过程,虚假url会跳转至大文件(如下载蓝光电影)从而占用网络IO
    解决方法:分析爬虫运行状态,多线程时可以分析线程运行时间,时间过长直接终止线程
    
    • 运维平台综合审计

      爬虫工程师与运维工程师的之间的对抗

基于加密数据

  • 对响应中的数据进行特殊化处理

    • 通过自定义字体(可以切换到手机端抓包进行翻译)

    • css偏移量

    反爬原理:源码数据不是真实数据,通过css位移才能获得真实数据
    解决方法:计算css位移
    
    • js动态生成数据(解析js,模拟产生数据)

    • 数据图片化(解析图片)

    • 编码格式(多编码解析)

验证码处理

图片验证码

  • 验证码:区分用户是计算机还是人的程序

  • 作用:防止恶意破解密码,刷票等

  • 使用场景:注册,登录,频繁发生请求

  • 处理方案:手动输入,图像识别引擎,打码平台

图片识别引擎

OCR:使用扫描仪或者数码相机对文本资料进行扫描成图像文件,然后对图像文件进行分析处理,自动识别获取文字信息及版面信息的软件

  • tesseract:一款由HP实验室开发由google维护的OCR引擎,开源免费,支持多语言多平台

  • 安装教程

  • example:

from PIL import Image
import pytesseract

im = Image.open('../Data/1.png')

result = pytesseract.image_to_string(im)

print(result)

打码平台

以下打码平台已经被禁止,虽然网上还有其他平台,但仍会遭受打击且收费,这里不在赘述使用方法

JS的解析

确定js的位置

  • 对手机端进行抓包分析更简单

  • 通过initiator定位到js文件

  • 通过搜索关键字定位到js文件

  • 通过元素绑定的事件定位到js文件

分析js代码(逆向)

没啥好讲的pass(极其头疼的一部分,劝退)

模拟重现

  • 通过第三方js加载模块直接加载js运行
import js2py


# 创建js环境对象
js = js2py.EvalJs()

js.execute(''' 
        function fun (e) {
            var r = "" + (new Date).getTime()
            return {
                ts: r,
            }
        };''')

js.execute('console.log("hello world")')
js.execute('''console.log(fun())''')
  • 用python重现js过程(要求扎实的逆向能力和良好的python素养)

这一部分感兴趣的自己学

数据库

框架

分布式

appium(移动设备爬取)

感言

转方向了,累了,已经可以做些low爬虫了

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值