Tip:我写了一篇直接构造请求获取微博数据的文章,不使用selenium,直接访问url获取到json数据,然后解析即可得到想要的数据的文章,请参考微博博主动态及相册的请求构造规律
=============================以下是正文============================
我们在浏览一些网页的时候,鼠标滚到底,就又会加载出一些新的内容,但是请求的网址是没有变的,这就是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()去掉空格和换行
有些朋友私信我要源码,这是去年写的了,还能不能跑各自去改吧:
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
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import NoSuchElementException, TimeoutException
def LoGoin():
'''
登录操作
:return: None
'''
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()
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)
while True:
try:
# 判断验证码是否存在,若存在则输入验证码
code = wait.until(EC.presence_of_element_located((By.XPATH, '//div[@class="item verify"]/input'))) # 验证码输入框
time.sleep(2)
code.click()
code.send_keys('aaa') # 先模拟输入,一会儿输入正确的验证码时,要先清空这个输入框
text_code = input('请输入验证码:')
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) # 输完验证码回车即可登录
break
except NoSuchElementException:
print('不需要验证码')
break
try:
if wait.until(EC.element_to_be_clickable((By.XPATH, '//strong[@node-type="follow"]'))):
# 这个节点是登录成功之后才会显示的
print('登录成功')
except NoSuchElementException or TimeoutException:
print('登录失败\n')
def craw_pic():
'''
爬取图片
:return: 所有完整图片的链接(经过构造了)
'''
wait = WebDriverWait(driver, 25)
url = 'https://weibo.com/p/1005052331498495/photos?from=page_100505&mod=TAB#place'
driver.get(url) # 在该新的窗口直接访问微博相册
print('正在努力访问相册.....')
time.sleep(15)
pictures = driver.find_elements_by_xpath('//div[@class="photo_cont"]/a/img')
js = "document.documentElement.scrollTop=500000"
for i in range(50):
try:
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
except Exception as e:
print(e)
print('访问成功')
if pictures:
print('相册总共有{}张图'.format(len(pictures)))
pics = []
for pic in pictures:
try:
cen_pic = pic.get_attribute('src')
# print('获取属性')
new_cen_pic = re.compile('\/\/.*?\/.*?\/(.*?)\?.*?').findall(cen_pic)
pics.append('https://wx1.sinaimg.cn/mw1024/'+new_cen_pic[0])
# print('构造链接')
except Exception as e:
print(e)
# print('返回')
return pics
def save_pic(pics):
print('开始下载图片.....')
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML,'
' like Gecko) Chrome/68.0.3440.106 Safari/537.36'}
proxy = [{'http': 'http://180.110.212.36:8118'}, {'http': 'http://122.246.49.135:8010'},
{'http': 'http://111.155.116.245:8123'}, {'http': 'http://123.56.169.22:3128'}]
proxies = random.choice(proxy)
print(proxies)
for i in range(len(pics)):
try:
img = requests.get(pics[i], headers=headers, proxies=proxies)
if i % 20 == 0:
time.sleep(5)
if img.status_code == 200:
with open('D:/Projects/p1/pictures/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)))
except Exception as e:
print('失败原因:'.format(e))
if __name__ == '__main__':
t1 = time.time()
driver = webdriver.Chrome()
LoGoin()
t2 = time.time()
pic_list = craw_pic()
t3 = time.time()
save_pic(pic_list)
t4 = time.time()
print('爬虫用时'.center(20, '='))
print('登录用时{}秒\n'.format(t2-t1))
print('解析用时{}秒\n'.format(t3-t2))
print('下载{}张图片用时{}秒\n'.format(len(pic_list), t4-t3))
print('完成'.center(20, '='))