selenium爬取Ajax加载的网页(以微博为例)

我们在浏览一些网页的时候,鼠标滚到底,就又会加载出一些新的内容,但是请求的网址是没有变的,这就是Ajax加载的效果。一般去爬取这种网站的时候,往往只能得到一开始加载出来的那些内容,而要利用鼠标滚到底才能继续加载出来的内容是得不到的,所以今天利用selenium来模拟用户登录微博,并模拟鼠标下拉抓取某博主的相册。

准备工作:安装并配置好Python的环境,安装了selenium和浏览器驱动,因为我是用Chrome,所以我就以Chrome来说明了,其他的方法类似。

安装python和配置就不用说了,selenium的安装和简单的使用请见 selenium安装和使用

思路:模拟用户登录微博(半自动),搜索博主,然后点击她的相册,先获取一次加载出来的相册的照片,然后selenium自动滚动鼠标,再一次获取加载出来的相册的照片,两次的长度(len)如果不一样,说明每次滚动鼠标之后都有新数据加载出来,所以继续循环滚动——获取数据——判断的过程,直到这一次和上一次的数据的长度一样,则说明加载完了,将得到的信息整理之后,利用requests库,下载图片

1. 导入相关的库,打开浏览器

import requests
import time
import re
import random
from selenium import webdriver
from selenium.webdriver.common.keys import Keys  # 输入框回车
from selenium.webdriver.common.by import By  # 与下面的2个都是等待时要用到
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import NoSuchElementException, TimeoutException  # 异常处理

driver = webdriver.Chrome()
url = 'https://weibo.com'
driver.get(url)
wait = WebDriverWait(driver, 25)
driver.maximize_window()  # 窗口最大化
login = wait.until(EC.element_to_be_clickable((By.XPATH, '//li/a[@node-type="loginBtn"]')))  # 登录按钮
login.click()

执行上面代码之后,将会自动打开Chrome,然后搜索微博主页,进入登陆页面。

2. 模拟用户输入用户名和密码,这儿有个问题,之前登陆好像不需要验证码,但是可能是登录的次数太频繁,所以有了验证码,先说说输入用户名和密码

执行到这儿,程序将会等待用户输入用户名和密码,回车后就是判断有没有验证码这一步,如果有,就输入验证码,没有就跳过

    try:
        username = input('请输入账号:')
        password = input('请输入密码:')
        user = wait.until(
            EC.presence_of_element_located((By.XPATH, '//div[@class="item username input_wrap"]/input')))
        user.send_keys(username)
        pw = wait.until(
            EC.presence_of_element_located((By.XPATH, '//div[@class="item password input_wrap"]/input')))
        pw.send_keys(password)
        pw.send_keys(Keys.ENTER)
    except Exception as e:
        print(e)

判断的是否有验证码的方法是,寻找验证码的输入框,如果找不到,则说明没有,那就可以直接登录了。当然,这儿要用异常处理,否则如果找不到这个输入框节点,程序就会退出了。

code = wait.until(EC.presence_of_element_located((By.XPATH, '//div[@class="item verify"]/input')))  # 验证码输入框

有些验证码我们自己也看不出来,所以这儿增加一个功能,就是看不清的话,可以选择换一张。实现的方法就是,如果输入的字符串的长度小于2,会换下一张,因为验证码都不可能是2个或更少的字符的,所以你随意输入一个字母或数字回车就可以看下一张图,直到输入正确的为止。

            if len(text_code) <= 2:
                next_code = wait.until(EC.element_to_be_clickable((By.LINK_TEXT, '换一张')))  # 看不清时,可点击换一张
                next_code.click()
                time.sleep(2)
            else:
                # print(text_code, type(text_code))
                code.clear()  # 清空验证码输入框
                code.send_keys(text_code)
                code.send_keys(Keys.ENTER)  # 输完验证码回车即可登录

这儿增加一个是否登录成功的判断

        if wait.until(EC.element_to_be_clickable((By.XPATH, '//strong[@node-type="follow"]'))):
            # 这个节点是登录成功之后才会显示的
            print('登录成功')

3. 登录成功之后,其实我们就可以直接利用浏览器访问目标网址了(相册)。没模拟登录之前,如果直接访问这个链接的话,得到的不是正确的链接,而现在我们已经成功登录,浏览器已经记住了登录的用户的登录信息(cookie),所以让浏览器访问这个链接就可以了

url = 'https://weibo.com/p/1005052331498495/photos?from=page_100505&mod=TAB#place'
driver.get(url)  # 在该窗口直接访问微博相册

然后获取第一次的数据 

pictures = wait.until(EC.presence_of_all_elements_located((By.XPATH, '//div[@class="photo_cont"]/a/img')))

利用循环,一次次的将鼠标滚到最下面,然后获取所有的图片的链接,根据两次的长度是否相等来判断网页是否已经加载完

driver.execute_script(js)
time.sleep(2)
pictures1 = wait.until(EC.presence_of_all_elements_located((By.XPATH, '//div[@class="photo_cont"]/a/img')))
if len(pictures1) == len(pictures):
    pictures = pictures1
    break
else:
    pictures = pictures1
    continue

4. 完成后,就会得到一个pictures列表,这个列表装的就是所有图片节点的信息,然后利用get_attribute()获取属性src(图片的链接就是这个属性的值)。

但是,得到的这些图片的网址只是小图的网址,而你可以将几张对应的小图和大图的网址放在一起对比,找出规律之后,就可以利用小图的地址构造出大图的地址,然后就是图片的下载了。这里要注意的就是下载图片,文件的打开方式是'wb',写入方式是.content;如果全是用selenium来实现的话还好,但是现在用requests去访问图片的链接,所以基本的防止被封IP的步骤还是要有,这儿增加了一个请求头(模拟浏览器),还有代理IP;代理IP的实现:去代理IP的网站找一些稳定的IP,然后构造成标准的proxies之后,利用random库下的choice方法,实现每次都是随机的挑选一个IP来访问,这样会降低被封的可能。另外,我们爬人家的网站,也不要太狠,频率降低一点,友好一点。

            img = requests.get(pics[i], headers=headers, proxies=proxies)
            if i % 20 == 0:
                time.sleep(5)
            if img.status_code == 200:
                with open('pic'+str(i+1)+'.jpg', 'wb') as f:
                    f.write(img.content)
                    # print('第{}张图爬取成功'.format(str(i+1)).center(20, '='))
            else:
                print('第{}张图爬取失败'.format(str(i+1)))

总结:

(1)   整个过程基本都需要进行异常处理,因为只要有一步出错,程序就终止了;

(2)  使用selenium有一个很重要的点,就是等待的问题, 因为网页加载是需要时间的,受网速的影响。我的网速很慢,所以我设置的等待时间都很相对较长。关于selenium的等待问题还有鼠标的各种操作, 有一篇文章写的很好,转自:selenium_对浏览器操作、鼠标操作等总结

(3)  这一次爬取的和以前的相比,最明显的就是可以爬取需要登录的网页了,当然目前验证码的识别还没说,所以才说这是半自动登录方式;

(4)  可以爬取Ajax加载的网页了;

(5)  学到的知识点:

 

1>>wait = WebDriverWait(driver, 25),最长等待时间设置为25s。

2>>wait.until(EC.presence_of_all_elements_located((By.XPATH, '//div[@class="photo_cont"]/a/img'))),(By.XPATH)等待条件是xpath 里的所有节点出现

3>>wait.until(EC.element_to_be_clickable((By.LINK_TEXT, '换一张'))) ,clickable,可点击,即等待By.LINK_TEXT节点可以点击。

4>>wait.until(EC.element_to_be_clickable((By.XPATH, '//strong[@node-type="follow"]'))) ,与第三点一样的意思,只是获取节点的语法不同而已。等待条件可点击

5>>'''driver.executu_script('window.open()')   # 打开新窗口
                    print('打开窗口')
                    driver.switch_to_window(driver.window_handles[1])  # 切换到该窗口
                    print('切换到窗口')
                ''' ,不知道为什么,pycharm里不能用这种方法打开一个新的窗口,但是在python自带的编辑器里是可以的。

6>>js = "document.documentElement.scrollTop=500000" # scrollTop的值就是距离网页顶端的像素值,0就表示在顶端,
driver.execute_script(js),滚动鼠标的操作

7>>利用滚动鼠标+循环+判断两次得到的节点的长度来实现Ajax加载网页的爬取,结合第二点

8>>selenium中的xpath的使用和常规在比如requests中的使用是有一定区别的。在requests中使用时,可以直接获取到某个标签的文本或属性的信息,比如html等于下面这一段

如果使用一般的方法,使用xpath要获得“陌生人说的情话,最为致命”这句话的表达式应该是

html.xpath('//div[@class="content"]/a/text()')

这样就可以直接获取到,没问题,那么在selenium中呢

比如我这样写

browser.find_element_by_xpath('//div[@class="content"]/a/text()')那么你得到的可能是下面的这种错误提示

同样,如果你想获取的是这个节点的某个属性值呢,第一种情况下,你可以直接html.xpath('//div[@class="content"]/a/@class')这样就可以得到这个节点的class属性的值,但是在selenium中同样是不行的。这是因为selenium它得到的是一个element,是一个节点,而不能直接得到这个节点的值。那么在selenium中如何获取,某个节点的属性和文本内容呢。

input = browser.find_element_by_xpath('//div[@class="content"]/a')
input.get_attribute('class')  # 得到这个节点的class属性的值
input.get_attribute('textContent')  # 方法一:得到这个节点的文本信息
print(input.text)  # 方法二:得到这个节点的文本信息

这样就可以得到一个字符串,内容就是节点的文本信息。有时这个信息可能会有一些多余的空格或换行,因为这是字符串,所以可以利用字符串的.strip()去掉空格和换行

猜你喜欢

转载自blog.csdn.net/wtwcsdn123/article/details/82023720
今日推荐