1.冻鸡
原来做过知乎的模拟登录,验证码识别,感觉知乎还是蛮复杂的。今天突然兴起,想爬下知乎,看看是否有意外收获。之前已有人爬轮子哥粉丝,今天就爬一下知乎大V张佳玮的回答页面吧。
作为小练习,爬取上面问题与完整回答,再爬一下赞同数与评论数,然后保存到mysql数据库。
2.分析
经测试
该网页翻页只需改动页码,规律增1;
每个页面有十个回答;
完整内容需要点击,不然只显示索引,就是上图能看到的部分;
未登录状态下,下面的页码不显示下一页,只能修改url来实现翻页。
3.开始工作
3.1.值得注意的点
对于这样婶的
怎样获得文字内容呢?直接copy button的css选择器即可,它的解析语句是copy的结果加::text
3.2.知乎回答页,前面只有一部分文字,并不是完整回答,它的元素是这样的
很明显,不执行点击是无法获得全部内容的,执行点击以后会多出很多标签。对于点击后的内容获取也和上面所述一致,子元素获取太复杂,就获取父元素,然后再用正则剔除掉无用的HTML标签。
3.3.cue下正则表达式
re.search 扫描整个字符串并返回第一个成功的匹配。
re.match只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回None;而re.search匹配整个字符串,直到找到一个匹配。
Python 的 re 模块提供了re.sub用于替换字符串中的匹配项。
一个小例子:
import re
phone = "2004-959-559 # 这是一个国外电话号码"
# # 删除字符串中的 Python注释
num = re.sub(r'#.*$', "", phone)
print("电话号码是: ", num)
# 删除非数字(-)的字符串
num = re.sub(r'\D', "", phone)
print("电话号码是 : ", num)
上面的意思很明显了,第一个将“#”号及以非换行符结尾的任意字符,替换为空字符串;
第二个将非数字替换为空字符串
4.写代码吧
4.1.执行每个回答的点击动作
使用chrome浏览器开发者工具(F12)定位不同回答的元素,复制下来查看其规律:
"#Profile-answers > div:nth-child(2) > div:nth-child(1) > div > div.RichContent.is-collapsed > div.RichContent-inner"
"#Profile-answers > div:nth-child(2) > div:nth-child(2) > div > div.RichContent.is-collapsed > div.RichContent-inner"
发现第一个回答与第二个回答只是在div的子元素在变,其余都一样,尝试构建可变的点击元素。
代码如下:
def get_pagesource(self, url):
self.driver.get(url=url)
self.driver.maximize_window()
time.sleep(5)
# 执行点击动作
for i in range(1, 11):
content_click = '#Profile-answers > div:nth-child(2) > div:nth-child('+str(i)+\
') > div > div.RichContent.is-collapsed > div.RichContent-inner'
try:
complete_content = WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, content_click)))
complete_content.click()
time.sleep(1)
except:
pass
pagedata = self.driver.page_source
return pagedata
上面代码比较关键的一点是执行完点击动作以后要返回网页源代码给其他函数调用。上面用了一个显示等待WebDriverWait(driver, 秒数).until(EC.presence_of_element_located((By.XX, 'XX'))),until用来检测指定元素是否出现,如果在超时时间内出现则返回选择器信息,否则报出TimeoutException异常。
想要了解更多关于”selenium等待“的内容,请点我
4.2.对于完整回答的处理
对完整回答的处理,使用css选择器获取到的内容是一个包含有很多HTML标签的列表,首先需要将列表变成字符串,然后再把字符串中的HTML标签删掉。写一个正则处理函数:
def handle_content(self, content_info):
content_f = "".join(content_info)
content_reg = re.compile(r'<[^>]+>', re.S)
content = content_reg.sub('', content_f)
return content
该函数可以将html标签去掉。
解决了上面的问题,接下来几乎一马平川了。
将想要获得的内容稍稍解析一下,然后保存到mysql数据库。
完整代码如下:
# -*- coding: utf-8 -*-
# @AuThor : frank_lee
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
import time
from scrapy.selector import Selector
import re
import pymysql
class zhihu_answer():
"""
"""
def __init__(self):
self.driver = webdriver.Chrome()
self.wait = WebDriverWait(self.driver, 30) # 设置超时时间
# 以下保存到Mysql数据库,不想要可以删掉
self.db = pymysql.connect("localhost", "root", "", "test")
self.cursor = self.db.cursor()
# 创建一个表
sql = """Create table If Not Exists zhangjiawei (
title varchar(50) not null ,
votes varchar(20) not null,
comment varchar(20) not null,
content longtext not null
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
"""
try:
# 执行sql语句
self.cursor.execute(sql)
# 提交到数据库执行
self.db.commit()
except:
# 如果发生错误则回滚
self.db.rollback()
# mysql部分结束
def get_pagesource(self, url):
self.driver.get(url=url)
self.driver.maximize_window()
time.sleep(5)
# 执行点击动作
for i in range(1, 11):
content_click = '#Profile-answers > div:nth-child(2) > div:nth-child('+str(i)+\
') > div > div.RichContent.is-collapsed > div.RichContent-inner'
try:
complete_content = self.wait.until(
EC.presence_of_element_located((By.CSS_SELECTOR, content_click)))
complete_content.click()
time.sleep(1)
except:
pass
pagedata = self.driver.page_source
return pagedata
def parse_content(self, data):
response = Selector(text=data) # 这里如果不使用"text=data",直接写data将会报错 'str' object has no attribute 'text'
infodata = response.css("#Profile-answers > div:nth-child(2) > div")
for infoline in infodata:
title = infoline.css("div h2 div a::text").extract_first("")
votes = infoline.css("div > div.RichContent > div > span > button.Button.VoteButton.VoteButton--up::text").extract_first("")
comment = infoline.css("div > div.RichContent > div > button:nth-child(2)::text").extract_first("")
content_f = infoline.css("div > div.RichContent > div.RichContent-inner > span").extract()
content = self.handle_content(content_f)
# 为保存到mysql做的处理,不想保存可以删掉
result = {
"title": title,
"votes": votes,
"comment": comment,
"content": content
}
# 保存到mysql数据库的操作,可选
insert_sql = """
insert into zhangjiawei(title, votes, comment, content) values(%s, %s, %s, %s);
"""
try:
# 执行sql语句
self.cursor.execute(insert_sql,
(result["title"], result["votes"], result["comment"], result["content"]))
# 提交到数据库执行
self.db.commit()
except:
# 如果发生错误则回滚
self.db.rollback()
print(result['title']+"--"+result['votes']+"--"+result['comment'])
def handle_content(self, content_info):
content_f = "".join(content_info)
content_reg = re.compile(r'<[^>]+>', re.S)
content = content_reg.sub('', content_f)
return content
if __name__ == '__main__':
z = zhihu_answer()
for i in range(1, 5):
url = 'https://www.zhihu.com/people/zhang-jia-wei/answers?page='+str(i)
data = z.get_pagesource(url)
z.parse_content(data=data)
time.sleep(5)
5.执行结果:
上图是PHPmyadmin数据库管理工具的显示结果,上面内容部分没有完全显示,想要它完全显示,就点一下title上方的选项,然后点完整内容,执行一下,就OK了。
参考文章:http://www.runoob.com/python/python-reg-expressions.html
https://www.cnblogs.com/themost/p/6900852.html
https://www.jb51.net/article/92672.htm