爬虫战知乎之selenium

写在开始

本教程仅供学习,若被他人用于其他用途,与本人无关

第一战-selenium

使用selenium可以直接获得加载后的网页信息而无需考虑请求信息,Ajax,js解密等等,可谓偷懒神器,下面以 https://www.zhihu.com/question/375762710 为例,进行实战讲解

  1. 初始

    分析网址,可以发现网址是https://www.zhihu.com/question/ + question_id的形式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    try:
    option = webdriver.ChromeOptions()
    option.add_argument("headless")
    option.add_experimental_option("detach", True)
    driver = webdriver.Chrome(chrome_options=option)
    driver.get('https://www.zhihu.com/question/'+question_id)

    except Exception as e:
    print(e)
    finally:
    driver.close()
  2. 关闭登录弹窗

    进入网页,结果发现了烦人的登录弹窗,可以通过Xpath和CSS选择器的方式获取关闭按钮,模拟点击操作

    图片

    1
    2
    3
    4
    5
    # 先触发登陆弹窗。
    WebDriverWait(driver, 40, 1).until(EC.presence_of_all_elements_located(
    (By.CLASS_NAME, 'Modal-backdrop')), waitFun())
    # 关闭登陆窗口
    driver.find_element_by_xpath('//button[@class="Button Modal-closeButton Button--plain"]').click()
  3. 获取所有答案

    更气人的来了,知乎竟然是动态加载,需要将滚轮滑动到底部才能获得全部答案,因此需要运行js获得页面高度,并模拟滚轮下滑

    sleep(1)是为了等待加载

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    def waitFun():
    js = """
    let equalNum = 0;
    window.checkBottom = false;
    window.height = 0;
    window.intervalId = setInterval(()=>{
    let currentHeight = document.body.scrollHeight;
    if(currentHeight === window.height){
    equalNum++;
    if(equalNum === 2){
    clearInterval(window.intervalId);
    window.checkBottom = true;
    }
    }else{
    window.height = currentHeight;
    window.scrollTo(0,window.height);
    window.scrollTo(0,window.height-1000);
    }
    },1500)"""
    driver.execute_script(js)
    def getHeight(nice):
    js = """
    return window.checkBottom;
    """
    return driver.execute_script(js)

    # 当滚动到底部时
    WebDriverWait(driver, 40, 3).until(getHeight, waitFun())

    # 等待加载
    sleep(1)
  4. 获取标题和副标题

    需要注意,有些问题是没有副标题的,因此也不存在副标题的element

    1
    2
    3
    title=driver.find_element_by_xpath('//h1[@class="QuestionHeader-title"]').text

    subtitle = driver.find_element_by_xpath('//span[@class="RichText ztext css-hnrfcf"]').text
  5. 展开评论

    难受,评论的数据并不包含在页面中,而需要点击 “XX条评论” 按钮后才进行动态加载,因此需要模拟点击这些按钮

    图片

    图片

    1
    2
    3
    4
    5
    6
    7
    wait=WebDriverWait(driver,30)   #显式等待
    path=(By.XPATH,'//button[@class="Button ContentItem-action Button--plain Button--withIcon Button--withLabel"]')
    clicks=wait.until(EC.presence_of_all_elements_located(path))
    for c in clicks:
    if '条' in c.text:
    driver.execute_script("arguments[0].click();", c)
    sleep(0.1)
  6. 按块获取答案

    先分析得到每一个问题的答主信息、回答内容、评论都在 “List-item”下

    图片

    然后在每一块元素的基础上,在进行答主信息、回答内容、评论的查找

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    # get blocks
    blocks=driver.find_elements_by_xpath('//div[@class="List-item"]')
    res=[]
    for b in blocks:
    author = b.find_element_by_xpath('.//div[@class="ContentItem-meta"]//meta[@itemprop="name"]').get_attribute('content')
    content = b.find_element_by_xpath('.//div[@class="RichContent-inner"]').text
    # button=b.find_element_by_xpath('.//div[@class="ContentItem-actions"]//button[contains(text(),"评论")]')
    button=b.find_element_by_xpath('.//button[@class="Button ContentItem-action Button--plain Button--withIcon Button--withLabel"]')
    if '收起' in button.text:
    comment = b.find_elements_by_xpath('.//ul[@class="NestComment"]')
    comments=''
    for c in comment:
    comments+=c.text
    print(comments)
    sleep(0.2)
    else:
    comments = '无评论'

    tmp={'author':author,'content':content,'comments':comments}
    res.append(tmp)
  7. 保存信息

    1. json

      1
      2
      3
      json_str = json.dumps(res)
      with open(filename+'.json', 'w',encoding='utf-8') as json_file:
      json_file.write(json_str)
    2. csv

      1
      2
      df = pd.DataFrame(res)
      df.to_csv(filename+'.csv')
    3. txt

      1
      2
      3
      4
      5
      6
      7
      8
      cwd=os.getcwd()
      with open(cwd++filename+'.txt','a',encoding='utf-8')as f:
      for i,r in enumerate(res):
      f.write(f'第{i+1}个回答'.center(30,'='))
      f.write('\n')
      f.write(f'author:{r["author"]}\n')
      f.write(f'content:{r["content"]}\n')
      f.write(f'comments:\n{r["comments"]}\n')
    4. 数据库

      通过pymysql或pymongo保存到数据库中,在此便不再多说了

  8. 优化

    通过selenium能够省力地爬取信息,但也会导致爬取速度的缓慢(个人理解是因为爬取速度要受网页加载速度的影响)

    为了更快速地爬取信息,可以采取多线程或多进程的爬取方式,以及更高级的分布式爬取技巧

  9. 结语

    不知道大家在爬取之前,是否注意到知乎遵循了robots协议(没有关注过的可以点击这个网址:https://www.zhihu.com/robots.txt)

    为了不给网站的管理员带来麻烦,希望大家在爬取的时候能尽量遵循robots协议;若在学习过程中在不可避免地无法遵循robots协议,也尽量维持爬虫爬取频率与人类正常访问频率相当,不过多占用服务器资源

写在最后

技术是无私的,非常不舍得把我这个实战代码分享了出去,毕竟以我的爬虫技术,编写这么个知乎爬虫实战的代码还是很费劲的,花了很多心血,也踩了很多坑

希望大家能够多多支持大可,有什么问题都可以提交,我也会及时为大家解决。最后也欢迎大家光临我的小站 https://cheungducknew.github.io/

不要打赏,只求关注呀QAQ