系列文章目录
上一篇:【Python Onramp】8.Python爬虫(1)基于requests和BeautifulSoup的全国区划数据爬虫
项目描述
如果使用上一节的requests,将不能实现对于知乎的有效爬取。由于知乎具有较强的反爬机制,所以我们需要换用一套可以用于更高程度模拟用户行为的框架,即selenium。
这个项目中,你将学习到selenium框架的使用,并利用selenium模拟用户行为爬取知乎热榜。
代码仓库https://github.com/Honour-Van/CS50/tree/master/WebSpider2
任务1
用手工登录配合的方法,获取登录知乎的 cookies ,供后续使用 cookies保存在./data/my_cookies.json
,后续程序使用时也指向这个位置,以便核查时更换其他cookies。这是一个单独的程序,和后续程序可以各自独立运行
- 使用已有的 cookies ,用 selenium 库登录知乎,抓取热榜话题和对应的热度值
基于如上任务,我们做出如下1_get_cookies.py
和2_hot_track.py
两个文件,分别对应两个指定任务。
要点总览
要点1:selenium的使用
我们这里使用的非常简单,可以在具体实现过程当中以练代讲。
这是一个参考https://blog.csdn.net/weixin_41931602/article/details/82754743
要点2:xpath方法筛选元素
在实现过程当中,我们本来想使用id或name等方法直接查找,但经常页面没有 id,name 这些属性值,而class name 重复性较高,link 定位有针对性,所以 Xpath 与 Css 定位更灵活些,见如下两篇博客中所述。
https://www.cnblogs.com/minieye/p/5803640.html
https://blog.csdn.net/qq_32189701/article/details/100176577
具体实现
selenium获取cookies
我们这里以获取cookies的程序来介绍selenium的用法。
我们这里以chrome为例,安装chromedriver的方法如下https://www.cnblogs.com/lfri/p/10542797.html
import json
from selenium import webdriver # selenium的浏览器控制器
from selenium.webdriver.chrome.options import Options #用于设置浏览器启动的一些参数
options = Options() # 这个选项类似于在Echarts中的Options,都是将相关属性通过键值对的方式组织在一起
#options.add_argument("--headless") # 不打开浏览器界面,以节省时间
browser = webdriver.Chrome(options=options) # 初始化一个浏览器控件
browser.get('https://www.zhihu.com/')
browser.maximize_window()
input("请用手机扫码登录,然后按回车……") # 等待用手机扫码登录, 登录后回车即可
cookies_dict = browser.get_cookies() # 获取当前登陆的cookies信息
cookies_json = json.dumps(cookies_dict) # cookies信息转化为json格式
#print(cookies_json)
# 登录完成后,将cookies保存到本地文件
out_filename = './data/my_cookies.json'
out_file = open(out_filename, 'w', encoding='utf-8')
out_file.write(cookies_json) # cookies信息保存
out_file.close()
print('Cookies文件已写入:' + out_filename)
browser.close()
一个经验是,如果不使用close,那么在notebook中进行selenium爬虫将会非常方便,每一个block都相当于是一个单独的操作行为,而窗口一直打开,也可以使用手动干预。
知乎热榜爬虫
由于实现还是比较困难,课程中的任务是将一个爬取京东信息手动改成知乎爬虫。
京东示例代码如下:
# -*- coding: utf-8 -*-
"""
Created on Sun Apr 26 00:08:48 2020
@author: Justin
"""
'''
在京东网站上搜索商品
保存当前页的商品信息后,翻到指定页码继续保存商品信息
'''
# 打开浏览器
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.by import By
from pyquery import PyQuery as pq
import time
import random
browser = webdriver.Chrome()
# browser.maximize_window()
wait = WebDriverWait(browser, 10) # 设置等待页面加载的最长超时时间
# ---函数:搜索关键词--------------------------
def search(keyword):
inbox = browser.find_element_by_id('keyword') # 查找搜索输入框(提前检查html源码确认id)
inbox.send_keys(keyword)
inbox.send_keys(Keys.ENTER)
wait.until(EC.presence_of_element_located(
(By.ID, 'J_bottomPage')), message='in function search') # 等待页面底部的元素加载完成
# ---函数:翻到下一页--------------------------
def next_page():
# 下面代码用于控制侧边滚动条,拉到页面底端
# 该页面很长,不这么操作,未显示在屏幕范围内的页面内容不会被加载
browser.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(1)
# 翻页动作
nextpage_btn = wait.until(EC.element_to_be_clickable(
(By.CSS_SELECTOR, '#J_bottomPage > span.p-num > a.pn-next > em')))
nextpage_btn.click()
print('翻到下一页')
# ---函数:翻到指定页,并等待页面加载完成--------------------------
def index_page(pg_num):
# 拉到页面底端,让页码输入框等元素得以加载
browser.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(1)
inbox = wait.until(EC.presence_of_element_located(
(By.CSS_SELECTOR, '#J_bottomPage > span.p-skip > input')),
message='in function index_page:inbox')
submit_btn = wait.until(EC.presence_of_element_located(
(By.CSS_SELECTOR, '#J_bottomPage > span.p-skip > a')),
message='in function index_page:submit button')
inbox.clear()
inbox.send_keys(str(pg_num))
submit_btn.click()
print('翻到第{}页'.format(pg_num))
# ---函数:等待指定页面加载完成---------------------
def wait_page_load(pg_num):
# time.sleep(1) # 若没有合适的元素用于判断页面加载完成,设置固定延时用于等待页面加载
wait.until(EC.text_to_be_present_in_element(
(By.CSS_SELECTOR, '#J_bottomPage > span.p-num > a.curr'), str(pg_num)),
message='in function wait_page_load') # 等待页面底部的当前页码出现
print('第{}页加载完成'.format(pg_num))
# ---函数:获取商品信息---------------------
def get_goods():
for i in range(100): # 慢慢向下滑动窗口,让所有商品信息加载完成
browser.execute_script('window.scrollTo(0, {});'.format(i*100))
time.sleep(0.1)
pq_doc = pq(browser.page_source)
for i in range(1, 61, 10): # 每页包含60个商品,仅解析一部分作为测试
# 这里只是打印部分商品信息,进一步应该保存到文件中
print('---{}---'.format(i))
item = pq_doc('#J_goodsList > ul > li:nth-child({})'.format(i))
print(item.find('.p-img a > img').attr('src'))
#print(item.find('.p-name.p-name-type-2 > a > em').text())
print(item.find('.p-price > strong > i').text())
# ----------------------------------------------
# ---主程序-------------------------------------
browser.get('https://search.jd.com')
search('冰箱') # 搜索产品信息
wait_page_load(1) # 等待第1页加载完成
get_goods() # 获取当前页面的产品信息
for i in range(2, 5): # 翻页并处理
time.sleep(1+random.random()) # 适当延时1.x秒,规避网站反爬虫机制
index_page(i) # 翻到第i页
wait_page_load(i) # 等待第i页加载完成
get_goods()
time.sleep(1)
browser.close()
更改为知乎热榜信息如下,同示例程序不同的是,等待选项需要自己另行构建。如果不等待,直接爬出内容,就会发现我们的
之前我们使用了查找按钮上文字的方法,这里我们观察到,热榜共50条,最下部有一个class为HotList-end的元素。
我们等待其加载好即可。
wait.until(EC.presence_of_element_located(
(By.XPATH, '//div[@class="HotList-end"]')), message="wait hotlist loading") # 等待页面底部的当前页码出现
具体爬取过程中,我们遇到了一些小麻烦,比如top3的class和后续元素的class不一定相同。
[<div class="HotItem-rank HotItem-hot">1</div>]
[<div class="HotItem-rank HotItem-hot">2</div>]
[<div class="HotItem-rank HotItem-hot">3</div>]
[<div class="HotItem-rank">4</div>]
[<div class="HotItem-rank">5</div>]
[<div class="HotItem-rank">6</div>]
[<div class="HotItem-rank">7</div>]
[<div class="HotItem-rank">8</div>]
[<div class="HotItem-rank">9</div>]
[<div class="HotItem-rank">10</div>
我们最先开始使用HotItem-rank HotItem-hot
整个作为类名查找,发现甚至不能找出前三个。但是后面的都可以通过 HotItem-rank
作为 class 名筛选出来。
最终代码如下:
import json
import time
from datetime import datetime
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.by import By
from bs4 import BeautifulSoup
options = Options()
# options.add_argument("--headless")
browser = webdriver.Chrome(options=options)
browser.get("https://www.zhihu.com")
browser.maximize_window()
browser.delete_all_cookies()
cookie_filename = "./data/my_cookies.json"
cookies_file = open(cookie_filename, 'r', encoding='utf-8')
cookies = json.load(cookies_file)
for cookie in cookies:
browser.add_cookie({
'domain': '.zhihu.com',
'name': cookie['name'],
'value': cookie['value'],
'path': '/',
'expires': None
})
browser.get("https://www.zhihu.com")
time.sleep(3)
browser.save_screenshot("./output/zhihu_login.png") # 保存图片
hot_button = browser.find_element_by_xpath("//a[@href='/hot']")
hot_button.click()
wait = WebDriverWait(browser, 10)
for i in range(100): # 慢慢向下滑动窗口,让所有商品信息加载完成
browser.execute_script('window.scrollTo(0, {});'.format(i*100))
time.sleep(0.1)
wait.until(EC.presence_of_element_located(
(By.XPATH, '//div[@class="HotList-end"]')), message="wait hotlist loading") # 等待页面底部的当前页码出现
browser.save_screenshot("./output/zhihu_hot.png")
f = open("./output/hot.txt", 'w', encoding='utf-8')
print("知乎热榜:", datetime.now().strftime("%Y-%m-%d %H:%M:%S"), file=f)
soup = BeautifulSoup(browser.page_source, 'html.parser')
for item in soup.select("section.HotItem"):
item_index = item.select("div.HotItem-rank")
print(item_index[0].get_text(), file=f)
item_title = item.select("h2.HotItem-title")
print("问题:", item_title[0].get_text(), file=f)
item_excerpt = item.select("p.HotItem-excerpt")
if len(item_excerpt):
print("概述:",item_excerpt[0].get_text(), file=f)
item_metrics = item.select("div.HotItem-metrics")
print("热度:",item_metrics[0].get_text()[:-7] + "万", file=f)
总结
一个思想:爬虫具有攻防性质。如果网站使用了更好的爬虫机制,就可以有相应的反爬机制,这当然是在一定的线度内的。
几个要点:
- xpath选择元素
- selenium爬虫框架