阿猫的博客

阿猫的博客

selenium+beautifulsoup+pandas爬取百度学术

505
2021-03-28

环境

requirements:
BeautifulSoup
selenium(with headless Chrome)
pandas

BeautifulSoup用来解释html,用selenium代替requests进行请求(原因后面会讲到),用pandas存储

搜索页url

打开百度学术输入关键字回车后可以看到当前的url是

http://xueshu.baidu.com/s?wd=自然语言处理&rsv_bp=0&tn=SE_baiduxueshu_c1gjeupa&rsv_spt=3&ie=utf-8&f=8&rsv_sug2=0&sc_f_para=sc_tasktype%3D%7BfirstSimpleSearch%7D

翻页后

http://xueshu.baidu.com/s?wd=自然语言处理&pn=10&tn=SE_baiduxueshu_c1gjeupa&ie=utf-8&sc_f_para=sc_tasktype%3D%7BfirstSimpleSearch%7D&sc_hit=1

很容易发现,实质上请求的url应该是http://xueshu.baidu.com/s?wd=自然语言处理&pn=x(x为条数) 复制进地址框中打开发现跟刚才的是一样的,ok

https://img-blog.csdnimg.cn/20190521132508511.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzQwOTMwOQ==,size_16,color_FFFFFF,t_70

于是可以根据这个规律来构造要请求的url

# 构造请求url
url = '<http://xueshu.baidu.com/s?wd=自然语言处理&pn=>'
urls = []
for i in range(0, 210, 10):
    urls.append(url + str(i))

详情页url

接下来这一部分就有一些难度了。 首先还是老办法用开发者工具看看链接所在的标签 ps:点击开发者工具左上角的鼠标工具可以快速定位页面中的元素!

https://img-blog.csdnimg.cn/20190521133433377.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzQwOTMwOQ==,size_16,color_FFFFFF,t_70

一开始觉得ok很好办 用request做就行 贴一下用request的代码

# 请求头
headers = {
    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko)'
                  'Chrome/65.0.3325.162 Safari/537.36'
}
# 请求
html = requests.get(urls[0], headers)
html.encoding = 'utf-8'
soup = BeautifulSoup(html.text, 'lxml')
# print(html.text)

#获取书的详情url
#匹配所有的url
book = soup.find_all(name='a', href=re.compile('/s.*?'),
                     attrs={'target': '_blank', 'data-click': "{'button_tp':'title'}"})
books = []
for item in book:
    book_url = item.get('href')
    books.append('<http://xueshu.baidu.com>' + book_url)
print(books)

这时候确实能够获取到所有的图书详情的url,但是从这个url跳转获得的页面信息是折叠的,无法获得完整的摘要等信息,这是很大的一个坑点。可能是一个简单的反爬机制。

再观察,从页面中获得的url是

http://xueshu.baidu.com/s?wd=paperuri%3A%286d66b7b4d828ef44f558745a76af01c2%29&filter=sc_long_sign&sc_ks_para=q%3D%E8%87%AA%E7%84%B6%E8%AF%AD%E8%A8%80%E5%A4%84%E7%90%86%E6%8A%80%E6%9C%AF%E5%9F%BA%E7%A1%80&sc_us=6796286780419649144&tn=SE_baiduxueshu_c1gjeupa&ie=utf-8

实际点击打开之后的url是

http://xueshu.baidu.com/usercenter/paper/show?paperid=6d66b7b4d828ef44f558745a76af01c2&site=xueshu_se

说明中间还发生了跳转,如果直接请求上一个url不能获得完整的页面信息 那如何获得真正的url呢?用selenium

Selenium是啥? Selenium 测试直接在浏览器中运行,就像真实用户所做的一样。Selenium 测试可以在 Windows、Linux 和 Macintosh上的 Internet Explorer、Chrome和 Firefox 中运行。其他测试工具都不能覆盖如此多的平台。使用 Selenium 和在浏览器中运行测试还有很多其他好处。

selenium和Chromedriver的安装部署可以参考 https://www.cnblogs.com/technologylife/p/5829944.html

然后就可以开始用selenium进行请求了,贴一下代码

from selenium import webdriver
from bs4 import BeautifulSoup
from lxml import etree

# 使用headless Chrome(phantomjs不可用)
option = webdriver.ChromeOptions()
option.add_argument('--headless')
option.add_argument('--disable-gpu')
driver = webdriver.Chrome(chrome_options=option)
driver.set_window_size(1920, 1080)

def get_page(url):
    # 打开网页
    driver.get(url)
    soup = BeautifulSoup(driver.page_source, 'lxml') # 同样用beautifulsoup对html进行处理

    # 得到原始url
    soup = str(soup)
    html = etree.HTML(soup)
    ori_urls_a = html.xpath('//div[@class="sc_content"]/h3/a/@href') # 用xpath进行选择
    ori_urls = []
    for url in ori_urls_a:
        ori_urls.append('<http://xueshu.baidu.com>' + url)

    for url in ori_urls:
        driver.get(url)
        real_url = driver.current_url
        print(real_url)
        

然后就可以得到真正的url了!

获取图书详情

这一步比较简单,还是用beautifulsoup就行了,贴一下代码

# -*- coding:utf-8 -*-
import requests
import re
from bs4 import BeautifulSoup
from selenium import webdriver

url = '<http://xueshu.baidu.com/usercenter/paper/show?paperid=7a211369a1fe18201be234ffb82622c1&site=xueshu_se>'
# 请求
# 使用headless Chrome
option = webdriver.ChromeOptions()
option.add_argument('--headless')
option.add_argument('--disable-gpu')
driver = webdriver.Chrome(chrome_options=option)
driver.set_window_size(1920, 1080)

def get_book_detail(url):
    # 先访问原url
    driver.get(url)
    soup = BeautifulSoup(driver.page_source, 'lxml')

    flag = 1
    # 先初始化成全局变量
    name = ''
    author_list = []
    abstract = ''
    keyword_list = []
    ref_num = 0

    # 书名
    try:
        name = soup.find(name='a', target='_blank',
                         attrs={'data-click': "{'act_block':'main','button_tp':'title'}"}).text
    except:
        name = None
        flag = 0  # 奇怪的标题 放弃该数据
    finally:
        print(name)

    if (flag == 1):
        # 作者
        # 此处作者需要用class来筛选避免选中下面参考文献中的作者
        try:
            authors = soup.find_all(name='a', target='_blank',
                                    attrs={'data-click': "{'button_tp':'author'}", 'class': ''})
            author_list = []
            for author in authors:
                pattern = re.compile('[\\u4e00-\\u9fa5]{2,4}')  # 正则表达式取出中文名
                author_name = pattern.findall(author.text)[0]
                author_list.append(author_name)
        except:
            authors = None
        finally:
            print(author_list)

        # 摘要
        # 此处需要作异常处理 遇到摘要为空时输出None
        try:
            abstract = soup.find(attrs={'class': 'abstract'}).text
        except:
            abstract = None
        finally:
            print(abstract)

        # 关键词
        try:
            url_pat = re.compile('^http://xueshu.baidu.com/.*?ie=utf-8$')
            keywords = soup.find_all(name='a', target='_blank', attrs={'href': url_pat, 'class': ''})
            for keyword in keywords:
                keyword_list.append(keyword.text)
        except:
            keyword_list = None
        finally:
            print(keyword_list)

        # 被引量
        ref = soup.find(name='a', target='_blank', attrs={'class': "sc_cite_cont"}).text
        ref_pat = re.compile('[0-9]{1,4}')
        ref_num = ref_pat.findall(ref)[0]
        print(ref_num)
    else:
        print("SKIPPED")

    return {'name': name, 'authors': author_list, 'abstract': abstract, 'keyword': keyword_list, 'ref': ref_num}

if __name__ == '__main__':
    get_book_detail(url)

PS: 要注意其中有一些信息可能会为空,因此需要做异常处理,保证脚本能够一直跑下去,同时跳过一些奇怪的数据。

pandas存储

最后,将数据用pandas存成csv或者xsl。 pandas的使用可以参考官方手册或者 https://blog.csdn.net/weixin_41666747/article/details/80249548 (简单版)

主要是Series(序列)和Dataframe(数据帧)两种数据结构 了解到只需要用列表嵌套字典的方法将数据传进去,用pd.DataFrame方法就可以将数据转换成一个二维数据结构。

最后步骤比较简单,贴下代码

import pandas as pd

res = []
for url in urls:
    res += bd.get_page(url)

columns = ['name', 'authors', 'abstract', 'keyword', 'ref']
df = pd.DataFrame(res, columns=columns)

df.to_excel('b.xls', encoding='utf-8') # xsl
df.to_csv('b.csv', encoding='utf-8') # csv

PS: 最后指定encoding=utf-8可以避免一些编码上的问题(习惯),但是直接保存为csv之后用Excel打开可能会出现乱码,这时候只需要用记事本打开csv然后另存为,选择utf-8再用Excel打开就可以正常显示了。