Python爬虫4-爬虫进阶


一、多线程爬虫

参考海燕Socket编程:https://www.cnblogs.com/haiyan123/p/8387770.html#lable7
有些时候,比如下载图片,因为下载图片是一个耗时的操作。如果采用之前那种同步的方式下载。那效率肯会特别慢。这时候我们就可以考虑使用多线程的方式来下载图片。

1.多线程介绍:

多线程是为了同步完成多项任务,通过提高资源使用效率来提高系统的效率。线程是在同一时间需要完成多项任务的时候实现的。
最简单的比喻多线程就像火车的每一节车厢,而进程则是火车。车厢离开火车是无法跑动的,同理火车也可以有多节车厢。多线程的出现就是为了提高效率。同时它的出现也带来了一些问题。更多介绍请参考:
https://zh.wikipedia.org/wiki/%E5%A4%9A%E7%BA%BF%E7%A8%8B

2.threading模块介绍:

threading模块是python中专门提供用来做多线程编程的模块。threading模块中最常用的类是Thread。以下看一个简单的多线程程序:

import threading
import time
def coding():
    for x in range(3):
        print('正在写代码%s'%threading.current_thread())
        time.sleep(1)
def drawing():
    for x in range(3):
        print('正在画图%s' % threading.current_thread())
        time.sleep(1)
def main():
    t1 = threading.Thread(target=coding,args=[])	#给函数添加参数,args要是列表或元组
    t2 = threading.Thread(target=drawing)
    t1.start()
    t2.start()
    #当前所有线程
    print(threading.enumerate())    
    #[<_MainThread(MainThread, started 6876)>, <Thread(Thread-1, started 3112)>, <Thread(Thread-2, started 14792)>]
if __name__ == '__main__':
    main()

2.1 线程Threading的属性和方法:

threading模块提供的一些方法:
  # threading.currentThread(): 返回当前的线程信息。
  # threading.enumerate(): 返回一个包含正在运行的所有线程的list。
  # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

2.2 Threading.Thread()实例对象方法:

Thread实例对象的方法
  # is_alive(): 返回线程是否活动的。True/False
  # getName(): 返回线程名。
  # setName(): 设置线程名。

示例如下:

from  threading import Thread
from multiprocessing import Process
import time,os,threading

def work():
    time.sleep(2)
    print('%s is running' % threading.currentThread().getName())
    print(threading.current_thread()) #当前线程
    print(threading.currentThread().getName()) #得到其他线程的名字
    
if __name__ == '__main__':
    t = Thread(target=work)
    t.start()
    print(threading.current_thread().getName())  #主线程的名字
    print(threading.current_thread()) #主线程
    print(threading.enumerate()) #连同主线程在内有两个运行的线程
    time.sleep(2)
    print(t.is_alive()) #判断线程是否存活
    print(threading.activeCount())
    print('主')


示例代码稍作修改:

from threading import Thread
from multiprocessing import Process
import time, os, threading
def work():
	# time.sleep(2)
	print('%s is running' % threading.currentThread().getName())
if __name__ == '__main__':
	t = Thread(target=work)
	t.start()
	print(threading.current_thread().getName())  # 主线程的名字
	print(threading.current_thread())  # 主线程
	print(threading.enumerate())  # 连同主线程在内有两个运行的线程
	time.sleep(2)
	print(t.is_alive())  # 判断线程是否存活
	print(threading.activeCount())
	print('主线程')


为了让线程代码更好的封装。可以使用threading模块下的Thread类,继承自这个类,然后实现run方法,线程就会自动运行run方法中的代码。示例代码如下:

import threading
import time
class CodingThread(threading.Thread):
    def run(self):
        for x in range(3):
            print('%s正在写代码' % threading.current_thread())
            time.sleep(1)
class DrawingThread(threading.Thread):
    def run(self):
        for x in range(3):
            print('%s正在画图' % threading.current_thread())
            time.sleep(1)
def multi_thread():
    t1 = CodingThread()
    t2 = DrawingThread()
    t1.start()
    t2.start()
if __name__ == '__main__':
    multi_thread()

3.多线程共享全局变量的问题

多线程都是在同一个进程中运行的。因此在进程中的全局变量所有线程都是可共享的。这就造成了一个问题,因为线程执行的顺序是无序的。有可能会造成数据错误。比如以下代码:

import threading
VALUE = 0
def add_value():
    global VALUE
    for x in range(1000000):
        VALUE += 1      #子作用域中修改全局变量时必须先声明 global VALUE
        
    print('value:%d'%VALUE)
def main():
    for x in range(2):
        t = threading.Thread(target=add_value)
        t.start()
if __name__ == '__main__':
    main()

以上结果正常来讲应该是1000000 2000000,但是因为多线程运行的不确定性。因此最后的结果可能是随机的。

4.锁机制:

为了解决以上使用共享全局变量(修改全局变量)的问题。threading提供了一个Lock类,这个类可以在某个线程访问某个变量的时候加锁,其他线程此时就不能进来,直到当前线程处理完后,把锁释放了,其他线程才能进来处理。示例代码如下:

import threading
VALUE = 0
gLock = threading.Lock()
def add_value():
    global VALUE
    gLock.acquire()
    for x in range(1000000):
        VALUE += 1
    gLock.release()
    print('value:%d'%VALUE)
def main():
    for x in range(2):
        t = threading.Thread(target=add_value)
        t.start()
if __name__ == '__main__':
    main()

注意:不要随便使用锁,只有多个线程修改全局变量时才需要加锁,如果只是访问那么不需要加锁。

5.Lock版本生产者和消费者模式:

生产者和消费者模式是多线程开发中经常见到的一种模式。生产者的线程专门用来生产一些数据,然后存放到一个中间的变量中。消费者再从这个中间的变量中取出数据进行消费。但是因为要使用中间变量,中间变量经常是一些全局变量,因此需要使用锁来保证数据完整性。以下是使用threading.Lock锁实现的“生产者与消费者模式”的一个例子:

import threading
import random
import time
gMoney = 1000
gLock = threading.Lock()
# 记录生产者生产的次数,达到10次就不再生产
gTimes = 0
class Producer(threading.Thread):
    def run(self):
        global gMoney
        global gLock
        global gTimes
        while True:
            money = random.randint(100, 1000)
            gLock.acquire()
            # 如果已经达到10次了,就不再生产了
            if gTimes >= 10:
                gLock.release()
                break
            gMoney += money
            print('%s当前存入%s元钱,剩余%s元钱' % (threading.current_thread(), money, gMoney))
            gTimes += 1
            time.sleep(0.5)
            gLock.release()
class Consumer(threading.Thread):
    def run(self):
        global gMoney
        global gLock
        global gTimes
        while True:
            money = random.randint(100, 500)
            gLock.acquire()
            if gMoney > money:
                gMoney -= money
                print('%s当前取出%s元钱,剩余%s元钱' % (threading.current_thread(), money, gMoney))
                time.sleep(0.5)
            else:
                # 如果钱不够了,有可能是已经超过了次数,这时候就判断一下
                if gTimes >= 10:
                    gLock.release()
                    break
                print("%s当前想取%s元钱,剩余%s元钱,不足!" % (threading.current_thread(),money,gMoney))
            gLock.release()
def main():
    for x in range(5):
        Consumer(name='消费者线程%d'%x).start()
    for x in range(5):
        Producer(name='生产者线程%d'%x).start()
if __name__ == '__main__':
    main()

6.Condition版的生产者与消费者模式:

Lock版本的生产者与消费者模式可以正常的运行。但是存在一个不足,在消费者中,总是通过while True死循环并且上锁的方式去判断钱够不够。上锁是一个很耗费CPU资源的行为。因此这种方式不是最好的。还有一种更好的方式便是使用threading.Condition来实现。threading.Condition可以在没有数据的时候处于阻塞等待状态。一旦有合适的数据了,还可以使用notify相关的函数来通知其他处于等待状态的线程。这样就可以不用做一些无用的上锁和解锁的操作。可以提高程序的性能。首先对threading.Condition相关的函数做个介绍,threading.Condition类似threading.Lock,可以在修改全局数据的时候进行上锁,也可以在修改完毕后进行解锁。以下将一些常用的函数做个简单的介绍:

  1. acquire:上锁。
  2. release:解锁。
  3. wait:将当前线程处于等待状态,并且会释放锁。可以被其他线程使用notifynotify_all函数唤醒。被唤醒后会继续等待上锁,上锁后继续执行下面的代码。
  4. notify:通知某个正在等待的线程,默认是第1个等待的线程。
  5. notify_all:通知所有正在等待的线程。notifynotify_all不会释放锁。并且需要在release之前调用。

Condition版的生产者与消费者模式代码如下:

import threading,time,random
gMONEY = 1000       #金库初始有1000元
gTotalTimes = 10        #定义生产者生成10次
gTimes = 0       #每生产一次,次数加一
gCondition = threading.Condition()
class Producer(threading.Thread):
	def run(self):
		global gMONEY
		global gTimes
		while True:
			money = random.randint(100,1000)
			gCondition.acquire()
			if gTimes<gTotalTimes:
				gTimes+=1
				gMONEY+=money
				gCondition.notify_all()     #通知所有正在等待的线程
				print("生产者%s 生产了%s 剩余总金额是%s"%(threading.currentThread().getName(),money,gMONEY))
			else:
				gCondition.release()
				break
			gCondition.release()
			time.sleep(0.5)
class Customer(threading.Thread):
	def run(self):
		global gMONEY
		while True:
			money = random.randint(100,1000)
			gCondition.acquire()
			while gMONEY<money:
				##可能一次生产的钱还不够消费的,或者生产的钱被其他消费者线程消费了造成余额不足
				##使用while循环,直到金库的钱够消费为止
				if gTimes>=gTotalTimes:
					gCondition.release()
					return          #此处使用return 直接跳出函数,不能使用break break只能跳出一层while循环
				print("%s准备消费%s元钱,剩余%s元钱,余额不足!"%(threading.currentThread(),money,gMONEY))
				gCondition.wait()
			gMONEY-=money
			print("%s消费%s元钱,剩余%s元钱" % (threading.currentThread(), money, gMONEY))
			gCondition.release()
			time.sleep(0.5)
def main():
	for x in range(3):
		t = Customer(name='消费者线程%s'%x)
		t.start()
	for x in range(5):
		t = Producer(name='生产者线程%s'%x)
		t.start()
if __name__ == '__main__':
	main()

打印结果:

<Customer(消费者线程0, started 17316)>消费836元钱,剩余164元钱
<Customer(消费者线程1, started 17876)>准备消费656元钱,剩余164元钱,余额不足!
<Customer(消费者线程2, started 3992)>准备消费768元钱,剩余164元钱,余额不足!
生产者生产者线程0 生产了575 剩余总金额是739
<Customer(消费者线程2, started 3992)>准备消费768元钱,剩余739元钱,余额不足!
<Customer(消费者线程1, started 17876)>消费656元钱,剩余83元钱
生产者生产者线程1 生产了470 剩余总金额是553
<Customer(消费者线程2, started 3992)>准备消费768元钱,剩余553元钱,余额不足!
生产者生产者线程2 生产了552 剩余总金额是1105
<Customer(消费者线程2, started 3992)>消费768元钱,剩余337元钱
生产者生产者线程3 生产了631 剩余总金额是968
生产者生产者线程4 生产了512 剩余总金额是1480
<Customer(消费者线程0, started 17316)>消费411元钱,剩余1069元钱
生产者生产者线程0 生产了730 剩余总金额是1799
<Customer(消费者线程1, started 17876)>消费934元钱,剩余865元钱
生产者生产者线程1 生产了802 剩余总金额是1667
<Customer(消费者线程2, started 3992)>消费559元钱,剩余1108元钱
生产者生产者线程2 生产了827 剩余总金额是1935
生产者生产者线程3 生产了319 剩余总金额是2254
生产者生产者线程4 生产了633 剩余总金额是2887
<Customer(消费者线程0, started 17316)>消费132元钱,剩余2755元钱
<Customer(消费者线程1, started 17876)>消费676元钱,剩余2079元钱
<Customer(消费者线程2, started 3992)>消费240元钱,剩余1839元钱
<Customer(消费者线程0, started 17316)>消费284元钱,剩余1555元钱
<Customer(消费者线程1, started 17876)>消费150元钱,剩余1405元钱
<Customer(消费者线程2, started 3992)>消费717元钱,剩余688元钱
<Customer(消费者线程0, started 17316)>消费441元钱,剩余247元钱
<Customer(消费者线程2, started 3992)>消费215元钱,剩余32元钱

7.Queue线程安全队列:

参考:https://docs.python.org/zh-cn/3.6/library/queue.html
在线程中,访问一些全局变量,加锁是一个经常的过程。如果你是想把一些数据存储到某个队列中,那么Python内置了一个线程安全的模块叫做queue模块。Python中的queue模块中提供了同步的、线程安全的队列类,包括FIFO(先进先出)队列Queue,LIFO(后入先出)队列LifoQueue即栈。这些队列都实现了锁原语(可以理解为原子操作,即要么不做,要么都做完),能够在多线程中直接使用。可以使用队列来实现线程间的同步。
队列对象 ([Queue](https://docs.python.org/zh-cn/3.6/library/queue.html#queue.Queue), [LifoQueue](https://docs.python.org/zh-cn/3.6/library/queue.html#queue.LifoQueue), 或者 [PriorityQueue](https://docs.python.org/zh-cn/3.6/library/queue.html#queue.PriorityQueue)) 提供下列描述的公共方法:

  1. queue.Queue(maxsize):创建一个先进先出的队列。

  2. queue.Queue.qsize():返回队列的大小。

  3. queue.Queue.empty():判断队列是否为空。

  4. queue.Queue.full():判断队列是否满了。

  5. queue.Queue.get():从队列中取最后一个数据(即最先进入队列的值)

  6. queue.Queue.put():将一个数据放到队列中。

  7. queue.Queue.task_done():表示前面排队的任务已经被完成。被队列的消费者线程使用。每个 get() 被用于获取一个任务, 后续调用 task_done() 告诉队列,该任务的处理已经完成。
    如果 join() 当前正在阻塞,在所有条目都被处理后,将解除阻塞(意味着每个 put() 进队列的条目的 task_done() 都被收到)。
    如果被调用的次数多于放入队列中的项目数量,将引发 ValueError 异常 。

  8. queue.Queue.join():阻塞至队列中所有的元素都被接收和处理完毕。
    当条目添加到队列的时候,未完成任务的计数就会增加。每当消费者线程调用 task_done() 表示这个条目已经被回收,该条目所有工作已经完成,未完成计数就会减少。当未完成计数降到零的时候, join() 阻塞被解除。

    方法参数:

    q.put(block=True):如果队列已满再设置值时会一直阻塞,直到队列大小小于maxsize(默认block为True)
    q.get(block=True):如果队列没有值了会一直阻塞(默认block为True)
    示例:

    import queue,threading,time
    def set_value(q):
    	index = 0
    	while True:
    		q.put(index)
    		print('向队列中添加了值:%s' % index)
    		index += 1
    		time.sleep(3)
    def get_value(q):
    	while True:
    		print(q.get())
    if __name__ == '__main__':
    	q = queue.Queue(4)
    	t1 = threading.Thread(target=set_value, args=[q])
    	t2 = threading.Thread(target=get_value, args=[q])
    	t1.start()
    	t2.start()

    8.使用生产者与消费者模式多线程下载表情包:

    生产者负责爬取图片url 和图片filename
    消费者负责将图片下载到本地
    当页面队列page_queue没有值时,生产者停止生产
    当页面队列page_queue没有值并且 图片队列img_queue也没有值时,消费者停止消费
    总结:从网络上获取url内容和图片存储到本地都是耗时的I/O操作,因此开启多个线程速度会加快很多。

    import threading
    import requests
    from lxml import etree
    from urllib import request
    import os
    import re
    from queue import Queue
    class Producer(threading.Thread):
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36'
        }
        def __init__(self,page_queue,img_queue,*args,**kwargs):
            super(Producer, self).__init__(*args,**kwargs)
            self.page_queue = page_queue
            self.img_queue = img_queue
        def run(self):
            while True:
                if self.page_queue.empty():
                    break
                url = self.page_queue.get()
                self.parse_page(url)
        def parse_page(self,url):
            response = requests.get(url,headers=self.headers)
            text = response.text
            html = etree.HTML(text)
            imgs = html.xpath("//div[@class='page-content text-center']//a//img")
            for img in imgs:
                if img.get('class') == 'gif':
                    continue
                img_url = img.xpath(".//@data-original")[0]
                suffix = os.path.splitext(img_url)[1]
                alt = img.xpath(".//@alt")[0]
                alt = re.sub(r'[,。??,/\\·]','',alt)
                img_name = alt + suffix
                self.img_queue.put((img_url,img_name))
    class Consumer(threading.Thread):
        def __init__(self,page_queue,img_queue,*args,**kwargs):
            super(Consumer, self).__init__(*args,**kwargs)
            self.page_queue = page_queue
            self.img_queue = img_queue
        def run(self):
            while True:
                if self.img_queue.empty():
                    if self.page_queue.empty():
                        return
                img = self.img_queue.get(block=True)
                url,filename = img
                request.urlretrieve(url,'images/'+filename)
                print(filename+'  下载完成!')
    def main():
        page_queue = Queue(100)
        img_queue = Queue(500)
        for x in range(1,101):
            url = "http://www.doutula.com/photo/list/?page=%d" % x
            page_queue.put(url)
        for x in range(5):
            t = Producer(page_queue,img_queue)
            t.start()
        for x in range(5):
            t = Consumer(page_queue,img_queue)
            t.start()
    if __name__ == '__main__':
        main()

    9.GIL全局解释器锁:

    Python自带的解释器是CPythonCPython解释器的多线程实际上是一个假的多线程(在多核CPU中,只能利用一核,不能利用多核)。同一时刻只有一个线程在执行,为了保证同一时刻只有一个线程在执行,在CPython解释器中有一个东西叫做GIL(Global Intepreter Lock),叫做全局解释器锁。这个解释器锁是有必要的。因为CPython解释器的内存管理不是线程安全的。当然除了CPython解释器,还有其他的解释器,有些解释器是没有GIL锁的,见下面:

  9. Jython:用Java实现的Python解释器。不存在GIL锁。更多详情请见:https://zh.wikipedia.org/wiki/Jython

  10. IronPython:用.net实现的Python解释器。不存在GIL锁。更多详情请见:https://zh.wikipedia.org/wiki/IronPython

  11. PyPy:用Python实现的Python解释器。存在GIL锁。更多详情请见:https://zh.wikipedia.org/wiki/PyPy
    GIL虽然是一个假的多线程。但是在处理一些IO操作(比如文件读写和网络请求)还是可以在很大程度上提高效率的。在IO操作上建议使用多线程提高效率。在一些CPU计算操作上不建议使用多线程,而建议使用多进程。

    10.多线程下载百思不得姐段子作业:

    import requests
    from lxml import etree
    import threading
    from queue import Queue
    import csv
    class BSSpider(threading.Thread):
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36'
        }
        def __init__(self,page_queue,joke_queue,*args,**kwargs):
            super(BSSpider, self).__init__(*args,**kwargs)
            self.base_domain = 'http://www.budejie.com'
            self.page_queue = page_queue
            self.joke_queue = joke_queue
        def run(self):
            while True:
                if self.page_queue.empty():
                    break
                url = self.page_queue.get()
                response = requests.get(url, headers=self.headers)
                text = response.text
                html = etree.HTML(text)
                descs = html.xpath("//div[@class='j-r-list-c-desc']")
                for desc in descs:
                    jokes = desc.xpath(".//text()")
                    joke = "\n".join(jokes).strip()
                    link = self.base_domain+desc.xpath(".//a/@href")[0]
                    self.joke_queue.put((joke,link))
                print('='*30+"第%s页下载完成!"%url.split('/')[-1]+"="*30)
    class BSWriter(threading.Thread):
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36'
        }
        def __init__(self, joke_queue, writer,gLock, *args, **kwargs):
            super(BSWriter, self).__init__(*args, **kwargs)
            self.joke_queue = joke_queue
            self.writer = writer
            self.lock = gLock
        def run(self):
            while True:
                try:
                    joke_info = self.joke_queue.get(timeout=40)
                    joke,link = joke_info
                    self.lock.acquire()
                    self.writer.writerow((joke,link))
                    self.lock.release()
                    print('保存一条')
                except:
                    break
    def main():
        page_queue = Queue(10)
        joke_queue = Queue(500)
        gLock = threading.Lock()
        fp = open('bsbdj.csv', 'a',newline='', encoding='utf-8')
        writer = csv.writer(fp)
        writer.writerow(('content', 'link'))
        for x in range(1,11):
            url = 'http://www.budejie.com/text/%d' % x
            page_queue.put(url)
        for x in range(5):
            t = BSSpider(page_queue,joke_queue)
            t.start()
        for x in range(5):
            t = BSWriter(joke_queue,writer,gLock)
            t.start()
    if __name__ == '__main__':
        main()

二、Ajax动态数据抓取

什么是AJAX:
AJAX(Asynchronouse JavaScript And XML)异步JavaScript和XML。过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。传统的网页(不使用Ajax)如果需要更新内容,必须重载整个网页页面。因为传统的在传输数据格式方面,使用的是XML语法。因此叫做AJAX,其实现在数据交互基本上都是使用JSON。使用AJAX加载的数据,即使使用了JS,将数据渲染到了浏览器中,在右键->查看网页源代码还是不能看到通过ajax加载的数据,只能看到使用这个url加载的html代码。

1.获取Ajax数据的方式:

  1. 直接分析ajax调用的接口。然后通过代码请求这个接口。
  2. 使用Selenium+chromedriver模拟浏览器行为获取数据。
    方式 优点 缺点
    分析接口 直接可以请求到数据。不需要做一些解析工作。代码量少,性能高。 分析接口比较复杂,特别是一些通过js混淆的接口,要有一定的js功底。容易被发现是爬虫。
    selenium 直接模拟浏览器的行为。浏览器能请求到的,使用selenium也能请求到。爬虫更稳定。 代码量多。性能低。

Selenium+chromedriver获取动态数据:
Selenium相当于是一个机器人。可以模拟人类在浏览器上的一些行为,自动处理浏览器上的一些行为,比如点击,填充数据,删除cookie等。chromedriver是一个驱动Chrome浏览器的驱动程序,使用他才可以驱动浏览器。当然针对不同的浏览器有不同的driver。以下列出了不同浏览器及其对应的driver:

  1. Chrome:https://sites.google.com/a/chromium.org/chromedriver/downloads
  2. Firefox:https://github.com/mozilla/geckodriver/releases
  3. Edge:https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/
  4. Safari:https://webkit.org/blog/6900/webdriver-support-in-safari-10/

安装Selenium和chromedriver:

  1. 安装Selenium:
    Selenium有很多语言的版本,有java、ruby、python等。我们下载python版本的就可以了。

    pip install selenium
  2. 安装chromedriver:下载chromedriver.exe之后,放到不需要权限的纯英文目录下就可以了。

    2.快速入门:

    现在以一个简单的获取百度首页的例子来讲下Seleniumchromedriver如何快速入门:

    from selenium import webdriver
    # chromedriver的绝对路径
    driver_path = r'D:\ProgramApp\chromedriver\chromedriver.exe'
    # 初始化一个driver,并且指定chromedriver的路径
    driver = webdriver.Chrome(executable_path=driver_path)
    # 请求网页
    driver.get("https://www.baidu.com/")
    # 通过page_source获取网页源代码
    print(driver.page_source)

    3.selenium常用操作:

    更多教程请参考:http://selenium-python.readthedocs.io/installation.html#introduction
    https://www.cnblogs.com/XJT2018/p/10317359.html

    关闭页面:

  3. driver.close():关闭当前页面。

  4. driver.quit():退出整个浏览器。

    定位元素:

    导入By:from selenium.webdriver.common.by import By

  5. find_element_by_id:根据id来查找某个元素。等价于:

    submitTag = driver.find_element_by_id('su')
    submitTag1 = driver.find_element(By.ID,'su')
  6. find_element_by_class_name:根据类名查找元素。 等价于:

    submitTag = driver.find_element_by_class_name('su')
    submitTag1 = driver.find_element(By.CLASS_NAME,'su')
  7. find_element_by_name:根据name属性的值来查找元素(一些input标签有name属性)。等价于:

    submitTag = driver.find_element_by_name('email')
    submitTag1 = driver.find_element(By.NAME,'email')
  8. find_element_by_tag_name:根据标签名来查找元素。等价于:

    submitTag = driver.find_element_by_tag_name('div')
    submitTag1 = driver.find_element(By.TAG_NAME,'div')
  9. find_element_by_xpath:根据xpath语法来获取元素。等价于:

    submitTag = driver.find_element_by_xpath('//div')
    submitTag1 = driver.find_element(By.XPATH,'//div')
  10. find_element_by_css_selector:根据css选择器选择元素。等价于:

    submitTag = driver.find_element_by_css_selector('//div')
    submitTag1 = driver.find_element(By.CSS_SELECTOR,'//div')

    注意,find_element是获取第一个满足条件的元素。find_elements是获取所有满足条件的元素。
    find_element_by_id这些方法使用Python写的,查找HTML元素的性能不如xpath,如果不需要和浏览器内容进行交互(如点击 或向输入框输入内容),可以将网页源代码扔给lxml来解析etree.HTML(driver.page_source)

    操作表单元素:

  11. 操作输入框:分为两步。第一步:找到这个元素。第二步:使用send_keys(value),将数据填充进去。示例代码如下:

    inputTag = driver.find_element_by_id('kw')
    inputTag.send_keys('python')
  12. 使用clear方法可以清除输入框中的内容。示例代码如下:

    inputTag.clear()
  13. 操作checkbox:因为要选中checkbox标签,在网页中是通过鼠标点击的。因此想要选中checkbox标签,那么先选中这个标签,然后执行click事件。示例代码如下:

    rememberTag = driver.find_element_by_name("rememberMe")
    rememberTag.click()
  14. 选择select:select元素不能直接点击。因为点击后还需要选中元素。这时候selenium就专门为select标签提供了一个类selenium.webdriver.support.ui.Select。将获取到的元素当成参数传到这个类中,创建这个对象。以后就可以使用这个对象进行选择了。示例代码如下:

    from selenium.webdriver.support.ui import Select
     # 选中这个标签,然后使用Select创建对象
     selectTag = Select(driver.find_element_by_name("jumpMenu"))
     # 根据索引选择
     selectTag.select_by_index(1)
     # 根据值选择
     selectTag.select_by_value("http://www.95yueba.com")
     # 根据可视的文本选择
     selectTag.select_by_visible_text("95秀客户端")
     # 取消选中所有选项
     selectTag.deselect_all()
  15. 操作按钮:操作按钮有很多种方式。比如单击、右击、双击等。这里讲一个最常用的。就是点击。直接调用click函数就可以了。示例代码如下:

    inputTag = driver.find_element_by_id('su')
     inputTag.click()

    行为链:

    有时候在页面中的操作可能要有很多步,那么这时候可以使用鼠标行为链类ActionChains来完成。比如现在要将鼠标移动到某个元素上并执行点击事件。那么示例代码如下:

    inputTag = driver.find_element_by_id('kw')
    submitTag = driver.find_element_by_id('su')
    actions = ActionChains(driver)
    actions.move_to_element(inputTag)
    actions.send_keys_to_element(inputTag,'python')
    actions.move_to_element(submitTag)
    actions.click(submitTag)
    actions.perform()

    还有更多的鼠标相关的操作。

  • click_and_hold(element):点击但不松开鼠标。
  • context_click(element):右键点击。
  • double_click(element):双击。

自动化测试中用的比较多,但是爬虫用的不多,了解即可
更多方法请参考:http://selenium-python.readthedocs.io/api.html

Cookie操作:

  1. 获取所有的cookie

    for cookie in driver.get_cookies():
    	print(cookie)
  2. 根据cookie的key获取value:

    value = driver.get_cookie(key)
  3. 删除所有的cookie:

    driver.delete_all_cookies()
  4. 删除某个cookie

    driver.delete_cookie(key)
  5. 设置cookie

    cookie2 = {"name":"BDUSS","value":"UJJTH5rcE9qd1VsWDZ5SzFsQkJVZUVPZTMwRi05bzg4UDE5YXJYZDAzNklkRzViQVFBQUFBJCQAAAAAAAAAAAEAAADzdT9HTGlseTAyNTE4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIjnRluI50Zbe"}
    driver.add_cookie(cookie2)

    页面等待:

    现在的网页越来越多采用了 Ajax 技术,这样程序便不能确定何时某个元素完全加载出来了。如果实际页面等待时间过长导致某个dom元素还没出来,但是你的代码直接使用了这个WebElement,那么就会抛出NullPointer的异常。为了解决这个问题。所以 Selenium 提供了两种等待方式:一种是隐式等待、一种是显式等待。

  6. 隐式等待:调用driver.implicitly_wait。那么在获取不可用的元素之前,会先等待10秒中的时间。示例代码如下:

    driver = webdriver.Chrome(executable_path=driver_path)
    driver.implicitly_wait(10)
    # 请求网页
    driver.get("https://www.douban.com/")
  7. 显示等待:显示等待是表明某个条件成立后才执行获取元素的操作。也可以在等待的时候指定一个最大的时间,如果超过这个时间那么就抛出一个异常。显示等待应该使用selenium.webdriver.support.excepted_conditions期望的条件和selenium.webdriver.support.ui.WebDriverWait来配合完成。示例代码如下:

    from selenium import webdriver
     from selenium.webdriver.common.by import By
     from selenium.webdriver.support.ui import WebDriverWait
     from selenium.webdriver.support import expected_conditions as EC
     driver = webdriver.Firefox()
     driver.get("http://somedomain/url_that_delays_loading")
     try:
         element = WebDriverWait(driver, 10).until(
             EC.presence_of_element_located((By.ID, "myDynamicElement"))
         )
     finally:
         driver.quit()
  8. 一些其他的等待条件:

    • presence_of_element_located:某个元素已经加载完毕了。
    • presence_of_all_emement_located:网页中所有满足条件的元素都加载完毕了。
    • element_to_be_cliable:某个元素是可以点击了。
      更多条件请参考:http://selenium-python.readthedocs.io/waits.html

示例:

切换页面:

有时候窗口中有很多子tab页面。这时候肯定是需要进行切换的。selenium提供了一个叫做switch_to_window来进行切换,具体切换到哪个页面,可以从driver.window_handles中找到。示例代码如下:

# 打开一个新的页面
self.driver.execute_script("window.open('"+url+"')")
# 切换到这个新的页面中
self.driver.switch_to_window(self.driver.window_handles[1])

switch_to_window和switch_to.window作用是一样的,selenium最新版本的推荐使用switch_to.window()
示例:

driver.get('https://www.baidu.com/')
# driver.get("https://www.douban.com/")     #直接在当前百度的页面打开豆瓣
driver.execute_script("window.open('https://www.douban.com/')")
print(driver.window_handles)
driver.switch_to_window(driver.window_handles[1])
print(driver.current_url)
print(driver.page_source)
# 虽然在窗口中切换到了新的页面。但是driver中还没有切换。
# 如果想要在代码中切换到新的页面,并且做一些爬虫。
# 那么应该使用driver.switch_to_window来切换到指定的窗口
# 从driver.window_handlers中取出具体第几个窗口
# driver.window_handlers是一个列表,里面装的都是窗口句柄。
# 他会按照打开页面的顺序来存储窗口的句柄。

设置代理ip:

demo10.py
有时候频繁爬取一些网页。服务器发现你是爬虫后会封掉你的ip地址。这时候我们可以更改代理ip。更改代理ip,不同的浏览器有不同的实现方式。这里以Chrome浏览器为例来讲解:

from selenium import webdriver
options = webdriver.ChromeOptions()
options.add_argument("--proxy-server=http://110.73.2.248:8123")
driver_path = r"D:\ProgramApp\chromedriver\chromedriver.exe"
driver = webdriver.Chrome(executable_path=driver_path,chrome_options=options)
driver.get('http://httpbin.org/ip')

WebElement元素:

demo8.py
from selenium.webdriver.remote.webelement import WebElement类是每个获取出来的元素的所属类。
源码分析

"""Represents a DOM element.
    Generally, all interesting operations that interact with a document will be
    performed through this interface.
    All method calls will do a freshness check to ensure that the element
    reference is still valid.  This essentially determines whether or not the
    element is still attached to the DOM.  If this test fails, then an
    ``StaleElementReferenceException`` is thrown, and all future calls to this
    instance will fail."""
代表一个DOM元素。一般来说,所有与文档交互的有趣的操作都将是通过这个接口执行。
所有的方法调用都会做一个新鲜度检查,以确保元素仍然有效。 这实质上决定了元素仍然连接到DOM。 如果这个测试失败,那么抛出 "StaleElementReferenceException",今后所有对这个例子会失败。
  • submitBtn.tag_name:获取标签名

  • submitBtn.text

  • submitBtn.click()

  • submitBtn.submit()

  • inputTag.clear()

  • get_attribute(“value”):这个标签的某个属性的值。

  • screentshot:获取当前页面的截图。这个方法只能在driver上使用。driver.save_screenshot('baidu.png')

  • is_selected():返回这个元素是否被选中

  • is_enabled():返回这个元素是否可用

  • 定位元素的一些方法:

    divTag.find_element_by_id()

  • inputTag.send_keys(“python”):想输入框input输入内容

  • inputTag.is_displayed():该标签是否可见

driver的对象类,也是继承自WebElement
更多请阅读源代码。
示例:

submitBtn = driver.find_element_by_id('su')
print(type(submitBtn))  #<class 'selenium.webdriver.remote.webelement.WebElement'>
print(submitBtn.get_attribute("value"))     #百度一下
driver.save_screenshot('baidu.png')     #保存屏幕截图

driver的属性和方法

from selenium import webdriver
from selenium.webdriver.remote.webelement import WebElement
driver_path = r"C:\programApps\chromedriver\chromedriver.exe"
driver = webdriver.Chrome(executable_path=driver_path)

我们使用Python内置函数dir()打印出driver的所有属性和方法:
print(dir(driver))

[‘NATIVE_EVENTS_ALLOWED’, ‘class’, ‘delattr’, ‘dict’, ‘doc’, ‘format’, ‘getattribute’, ‘hash’, ‘init’, ‘module’, ‘new’, ‘reduce’, ‘reduce_ex’, ‘repr’, ‘setattr’, ‘sizeof’, ‘str’, ‘subclasshook’, ‘weakref’, ‘_file_detector’, ‘_is_remote’, ‘_mobile’, ‘_switch_to’, ‘_unwrap_value’, ‘_wrap_value’, ‘add_cookie’, ‘application_cache’, ‘back’, ‘binary’, ‘capabilities’, ‘close’, ‘command_executor’, ‘create_web_element’, ‘current_url’, ‘current_window_handle’, ‘delete_all_cookies’, ‘delete_cookie’, ‘desired_capabilities’, ‘error_handler’, ‘execute’, ‘execute_async_script’, ‘execute_script’, ‘file_detector’, ‘find_element’, ‘find_element_by_class_name’, ‘find_element_by_css_selector’, ‘find_element_by_id’, ‘find_element_by_link_text’, ‘find_element_by_name’, ‘find_element_by_partial_link_text’, ‘find_element_by_tag_name’, ‘find_element_by_xpath’, ‘find_elements’, ‘find_elements_by_class_name’, ‘find_elements_by_css_selector’, ‘find_elements_by_id’, ‘find_elements_by_link_text’, ‘find_elements_by_name’, ‘find_elements_by_partial_link_text’, ‘find_elements_by_tag_name’, ‘find_elements_by_xpath’, ‘firefox_profile’, ‘forward’, ‘get’, ‘get_cookie’, ‘get_cookies’, ‘get_log’, ‘get_screenshot_as_base64’, ‘get_screenshot_as_file’, ‘get_screenshot_as_png’, ‘get_window_position’, ‘get_window_size’, ‘implicitly_wait’, ‘log_types’, ‘maximize_window’, ‘mobile’, ‘name’, ‘orientation’, ‘page_source’, ‘profile’, ‘quit’, ‘refresh’, ‘save_screenshot’, ‘session_id’, ‘set_page_load_timeout’, ‘set_script_timeout’, ‘set_window_position’, ‘set_window_size’, ‘start_client’, ‘start_session’, ‘stop_client’, ‘switch_to’, ‘switch_to_active_element’, ‘switch_to_alert’, ‘switch_to_default_content’, ‘switch_to_frame’, ‘switch_to_window’, ‘title’, ‘w3c’, ‘window_handles’]

总结一下常用的属性和方法如下:

  • 定位元素的方法

    • driver.find_element_by_class_name():通过class样式的名称获取想要的内容
    • dirver.find_element_by_id():通过id的名称获取想要的内容
    • driver.find_element_by_link_text():通过输入的文本内容获取对应的链接
    • driver.find_element_by_css_selector():通过css样式的名称获取想要的内容
    • driver.find_element_by_tag_name():通过标签的的名称获取想要的内容
    • driver.find_element_by_xpath():用过某个标签的xpath路径获取想要的内容

    注意:find_elements_by_xxx 是获取多个对象

  • driver.current_url:用于获得当前页面的URL

  • driver.title:用于获取当前页面的标题

  • driver.page_source:用于获取页面html源代码

  • driver.current_window_handle:用于获取当前窗口句柄

  • driver.window_handles:用于获取所有窗口句柄

  • driver.get(url):浏览器加载url。实例:driver.get(“http//:www.baidu.com")

  • driver.switch_to_frame(id或name属性值):切换到新表单(同一窗口)。若无id或属性值,可先通过xpath定位到iframe,再将值传给switch_to_frame()

  • driver.switch_to_window(窗口句柄):切换到新窗口

  • driver.execute_script(js):执行脚本命令

  • driver.close():关闭当前窗口,driver.quit():关闭浏览器,并且安全关闭session

  • driver.forward():浏览器向前(点击向前按钮)

  • driver.maximize_window():最大化浏览器窗口

  • driver.maximize_window():最大化浏览器窗口

  • driver.get_window_size():获取当前窗口的长和宽

  • driver.get_window_position():获取当前窗口坐标

  • driver.get_screenshot_as_file(filename):截取当前窗口

  • driver.implicitly_wait(s):隐式等待,通过一定的时长等待页面上某一元素加载完成。若提前定位到元素,则继续执行。若超过时间未加载出,则抛出NoSuchElementException异常

  • driver.switch_to.parent_frame():跳出当前一级表单。到达离它最近的frame

  • driver.switch_to_default_content():跳回最外层的页面

  • driver.switch_to_alert():警告框处理。处理JavaScript所生成的alert,confirm,prompt

  • driver.get_cookies():获取当前会话所有cookie信息

  • driver.get_cookie(cookie_name):返回字典的key为“cookie_name”的cookie信息

  • driver.add_cookie(cookie_dict):添加cookie。“cookie_dict”指字典对象,必须有name和value值

  • driver.delete_cookie(name,optionsString):删除cookie信息

  • driver.delete_all_cookies():删除所有cookie信息


4.selenium爬虫示例

4.1 淘宝登录

参考:模拟人工拖动(先快后慢)轨迹算法http://www.51testing.com/html/41/n-3725241-2.html
https://www.jianshu.com/p/f1fef22a14f4 有一些问题识别出来是机器拖动的
涉及到如下技术:
定位元素、操作表单元素、行为链(拖动滑块验证)

4.2 问卷星填报

https://www.jianshu.com/p/c34eaecd615f
https://www.wenjuan.com/report/statistical_report/5ec295223631f27b9d92cfaa?pid=5ec295223631f27b9d92cfaa
填表网站:https://www.wenjuan.com/s/7fA7RjT/

4.3 拉勾网数据

4.4 Boss直聘数据

Boss直聘数据不是通过Ajax发送的,直接查看网页源代码即可找到数据
https://www.zhipin.com/job_detail/?query=python&city=101280600&industry=&position=


三、验证码识别

阻碍我们爬虫的。有时候正是在登录或者请求一些数据时候的图形验证码。因此这里我们讲解一种能将图片翻译成文字的技术。将图片翻译成文字一般被成为光学文字识别(Optical Character Recognition),简写为OCR。实现OCR的库不是很多,特别是开源的。因为这块存在一定的技术壁垒(需要大量的数据、算法、机器学习、深度学习知识等),并且如果做好了具有很高的商业价值。因此开源的比较少。这里介绍一个比较优秀的图像识别开源库:Tesseract。

1.Tesseract:

Tesseract是一个OCR库,目前由谷歌赞助。Tesseract是目前公认最优秀、最准确的开源OCR库。Tesseract具有很高的识别度,也具有很高的灵活性,他可以通过训练识别任何字体。
安装:
Windows系统:
在以下链接下载可执行文件,然后一顿点击下一步安装即可(放在不需要权限的纯英文路径下):
https://github.com/tesseract-ocr/
Linux系统:
可以在以下链接下载源码自行编译。
https://github.com/tesseract-ocr/tesseract/wiki/Compiling
或者在ubuntu下通过以下命令进行安装:

sudo apt install tesseract-ocr

Mac系统:
Homebrew即可方便安装:

brew install tesseract

设置环境变量:
安装完成后,如果想要在命令行中使用Tesseract,那么应该设置环境变量。MacLinux在安装的时候就默认已经设置好了。在Windows下把tesseract.exe所在的路径添加到PATH环境变量中。
还有一个环境变量需要设置的是,要把训练的数据文件路径也放到环境变量中。
在环境变量中,添加一个TESSDATA_PREFIX=C:\path_to_tesseractdata\teseractdata

2.在命令行中使用tesseract识别图像:

如果想要在cmd下能够使用tesseract命令,那么需要把tesseract.exe所在的目录放到PATH环境变量中。然后使用命令:tesseract 图片路径 文件路径
示例:

tesseract a.png a

那么就会识别出a.png中的图片,并且把文字写入到a.txt中。如果不想写入文件直接想显示在终端,那么不要加文件名就可以了。

3.在代码中使用tesseract识别图像:

Python代码中操作tesseract。需要安装一个库,叫做pytesseract。通过pip的方式即可安装:

pip install pytesseract

并且,需要读取图片,需要借助一个第三方库叫做PIL。通过pip list看下是否安装。如果没有安装,通过pip的方式安装:

pip install PIL

使用pytesseract将图片上的文字转换为文本文字的示例代码如下:

# 导入pytesseract库
import pytesseract
# 导入Image库
from PIL import Image
# 指定tesseract.exe所在的路径
pytesseract.pytesseract.tesseract_cmd = r'D:\ProgramApp\TesseractOCR\tesseract.exe'
# 打开图片
image = Image.open("a.png")
# 调用image_to_string将图片转换为文字
text = pytesseract.image_to_string(image)
print(text)

4.用pytesseract处理拉勾网图形验证码:

import pytesseract
from urllib import request
from PIL import Image
import time
pytesseract.pytesseract.tesseract_cmd = r"D:\ProgramApp\TesseractOCR\tesseract.exe"
while True:
    captchaUrl = "https://passport.lagou.com/vcode/create?from=register&refresh=1513081451891"
    request.urlretrieve(captchaUrl,'captcha.png')
    image = Image.open('captcha.png')
    text = pytesseract.image_to_string(image,lang='eng')
    print(text)
    time.sleep(2)

5.打码平台识别验证码

推荐:
超级鹰 http://www.chaojiying.com/

四、Cookie

爬虫请求的网页有时需要携带请求首页时或登录后获取的cookie,目前有两种方式处理cookie

  • 手动处理

    将抓包工具中的cookie粘贴到请求headers中
    弊端:cookie有时效期,过了时效期又需要重新手动处理

  • 自动处理

    基于Session对象实现自动处理
    如何获取一个Session对象:requests.Session() 返回一个session对象
    session对象的作用:

    • 该对象可以像requests一样调用get/post发起请求,只不过如果在使用session发请求的过程中如果产生cookie,则cookie会自动存储在该session对象中,name就意味着下次再使用session对象发请求时,该请求会携带cookie
    • 在爬虫中使用session,session对象至少被使用几次?
      • 2次,第一次使用session是为了捕获cookie存储到session对象中,第二次使用session.get() 或session.post()发请求时携带上cookie

示例:雪球网请求数据需要携带访问首页时获取的cookie
首页:https://xueqiu.com/
获得数据发起的ajax请求:https://xueqiu.com/statuses/hot/listV2.json?since_id=-1&max_id=218185&size=15

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3472.3 Safari/537.36"
}

def xueqiu():
    session = requests.Session()
    main_url = "https://xueqiu.com/"
    session.get(url=main_url,headers=headers)	#1.获取cookie存储到session对象中

    url = "https://xueqiu.com/statuses/hot/listV2.json?since_id=-1&max_id=218185&size=15"
    #2.使用session对象发起请求时会携带原来存储的cookie
    page_text = session.get(url=url,headers=headers)
    print(page_text.json())

五、代理proxies

image.png
image.png
推荐购买的代理:
https://www.qg.net/ 青果代理
http://www.jinglingdaili.com/Shop-index.html 智联HTTP代理

image.png

六、模拟登陆

案例:模拟登陆古诗文网
登陆成功后展示收藏界面
image.png

import requests
from lxml import etree
from hashlib import md5
import os

headers = {
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36"
}

class Chaojiying_Client(object):
    def __init__(self, username, password, soft_id):
        self.username = username
        password =  password.encode('utf8')
        self.password = md5(password).hexdigest()
        self.soft_id = soft_id
        self.base_params = {
            'user': self.username,
            'pass2': self.password,
            'softid': self.soft_id,
        }
        self.headers = {
            'Connection': 'Keep-Alive',
            'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
        }

    def PostPic(self, im, codetype):
        """
        im: 图片字节
        codetype: 题目类型 参考 http://www.chaojiying.com/price.html
        """
        params = {
            'codetype': codetype,
        }
        params.update(self.base_params)
        files = {'userfile': ('ccc.jpg', im)}
        r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files, headers=self.headers)
        return r.json()

    def ReportError(self, im_id):
        """
        im_id:报错题目的图片ID
        """
        params = {
            'id': im_id,
        }
        params.update(self.base_params)
        r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers)
        return r.json()

def autoLoginGusiwen():
    #1.获取cookie
    base_url = "https://www.gushiwen.cn/"
    session = requests.session()
    session.get(base_url, headers=headers)

    #2.获取验证码图片,在任何可能获取cookie的地方我们都用session请求
    get_captcha = "https://so.gushiwen.cn/RandCode.ashx"
    captcha_data = session.get(get_captcha, headers=headers).content
    with open("captcha.jpg","wb") as fb:
        fb.write(captcha_data)

    #删除验证码图片
    # if os.path.exists("./captcha.jpg"):
    #     os.remove("./captcha.jpg")

    #3.验证码识别
    chaojiying = Chaojiying_Client('xiongjt', 'xr112358', '918609')
    im = open('captcha.jpg', 'rb').read()
    captcha_code = chaojiying.PostPic(im, 1902)['pic_str']
    print(captcha_data)

    data = {
        "__VIEWSTATE": "QYT6bITNDKjYdvVxdot7+vPqPoibPocr1Uma873iIyDBkxwn4ytNTF6GrEzvWhW6qUoWrDqq8h60wKlA/2v+RWbdkxODYjFlKvuTfQ8fb1wo4tpvv1ln5RLpR7k=",
        "__VIEWSTATEGENERATOR": "C93BE1AE",
        "from": "http://so.gushiwen.cn/user/collect.aspx?sort=t",
        "email": "15990076961",
        "pwd": "xr112358",
        "code": captcha_code,
        "denglu": "登录"
    }

    #4.登录
    login_url = "https://so.gushiwen.cn/user/login.aspx?from=http%3a%2f%2fso.gushiwen.cn%2fuser%2fcollect.aspx%3fsort%3dt"
    response = session.post(login_url,headers=headers,data=data)
    response.encoding = response.apparent_encoding
    with open("gusiwen.html","w",encoding="utf-8") as fw:
        fw.write(response.text)



if __name__ == '__main__':
    autoLoginGusiwen()

重点:
第4步 登陆post提交数据中有两组乱序的请求参数,最好去多次刷新页面手动测试登陆看是否为动态变化数据,
__VIEWSTATE
__VIEWSTATEGENERATOR

处理方式:

  • 常规来讲一般动态变化的请求参数会被隐藏在前台页面中,那么我们就要借助Chrome抓包工具去前台页面源码中搜索
  • 如果前台页面中没有,我们就要借助抓包工具进行全局搜索

image.png

七、使用百度AI

  • 图像识别
  • 语音识别&合成
  • 自然语言处理

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