123456798

"""
操作逻辑
1.处理关键词,列表循环输出,在出错时自动跳过关键词
#第一个关键字
2.使用selenium打开知网首页>>输入关键词>>点击搜索>>点击中文库(可能存在判断)>>点击改为显示50页
3.爬取信息>>保存信息>>翻页>>爬取信息>>翻页>>......
(处理验证码)
4.保存到MySQL和csv中
#下一个关键字
......
"""

import os, time, random, csv, requests, re, ddddocr
from bs4 import BeautifulSoup
from pymysql import Connection
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys


# 定义URL类
class URL:
    def __init__(
            self,
            url: str,
            title: str,
            author_name: str,
            source: str,
            publication_time: str,
            quote: str,
            database: str
    ):
        self.url = url  # 论文链接
        self.title = title  # 论文名
        self.author_name = author_name  # 作者姓名
        self.source = source  # 来源
        self.publication_time = publication_time  # 发表时间
        self.quote = quote  # 被引
        self.database = database  # 数据库


# 定义论文类
class Paper:
    def __init__(
            self,
            title: str,
            author_data: dict[str, list[str]],
            unit_data: dict[str, str],
            author_unit: dict[str, list[str]],
            abstract_text: str,
            keywords_data: list[str],
            name,
            time,
            content_string
    ):
        self.title = title  # 论文名
        self.author_data = author_data  # 作者姓名
        self.unit_data = unit_data  # 作者单位
        self.author_unit = author_unit  # 作者与对应的单位
        self.abstract_text = abstract_text  # 摘要
        self.keywords_data = keywords_data  # 关键字
        self.name = name  # 期刊名|会议名
        self.time = time  # 期刊时间|会议时间
        self.content_string = content_string  # 目录


# "处理关键词文本"
def get_keyword_list(my_path) -> list[str]:
    # 处理关键词文本
    my_keyword_list = []
    with open(my_path, 'r', encoding='utf-8') as f:
        for line in f:
            my_keyword_list.append(line.strip("\n").strip(";"))
    return my_keyword_list


# 使用selenium打开知网首页>>输入关键词>>点击搜索>>点击中文库(可能存在判断)>>点击改为显示50页
def driver_open(my_keyword_driver, keyword):
    # 进入知网首页并搜索关键字
    url = "https://www.cnki.net/"
    my_keyword_driver.get(url)  # 进入知网首页
    time.sleep(5)  # 等待网页加载
    # 将关键词输入搜索框
    my_keyword_driver.find_element(By.CSS_SELECTOR, '#txt_SearchText').send_keys(keyword)
    time.sleep(5)  # 等待输入完成
    # 点击搜索按钮
    my_keyword_driver.find_element(By.CSS_SELECTOR,
                                   'body > div.wrapper.section1 > div.searchmain > div > div.input-box > input.search-btn').click()
    time.sleep(7)
    # 点击中文按钮
    try:
        my_keyword_driver.find_element(By.CSS_SELECTOR,
                                       'body > div.wrapper > div.top-doctype > div > div > div > a.ch').click()
        time.sleep(4)
    except:
        # 在自动选择为总库或英文时点击中文按钮
        my_keyword_driver.find_element(By.CSS_SELECTOR,
                                       '#ModuleSearch > div:nth-child(2) > div > div > div > div > a.ch').click()
        time.sleep(4)
    # 点击20
    my_keyword_driver.find_element(By.CSS_SELECTOR, '#perPageDiv > div > i').click()
    # 点击50
    my_keyword_driver.find_element(By.CSS_SELECTOR, '#perPageDiv > ul > li:nth-child(3) > a').click()
    time.sleep(6)

    content = my_keyword_driver.page_source.encode('utf-8')  # 拿到搜索页面的源代码
    my_soup = BeautifulSoup(content, 'html.parser')
    return my_soup


# 爬取信息
def spider_url(
        my_soup,
        my_url_list: list,
        my_title_list: list,
        my_author_name_list: list,
        my_source_list: list,
        my_publication_time_list: list,
        my_quote_list: list,
        my_database_list: list
) -> tuple[list[str], list[str], list[str], list[str], list[str], list[str], list[str]]:
    try:
        # 获取当前页面的所有论文的URL类信息
        tbody = my_soup.find_all('tbody')  # 获取tbody标签
        tbody = BeautifulSoup(str(tbody[0]), 'html.parser')  # 解析出tbody标签

        # 得到title_list和url_list列表
        data_name = tbody.find_all('a', attrs={'class': 'fz14'})  # 获取所有的name标签下的a标签
        for i in data_name:
            # 获取论文url
            if "https://kns.cnki.net" in i['href']:
                url = i['href']
            else:
                url = 'https://kns.cnki.net' + i['href']
            # 获取论文标题
            title = i.text.strip("\n").strip()
            my_url_list.append(url)
            my_title_list.append(title)

        # 得到author_name_list
        data_author = tbody.find_all('td', attrs={'class': 'author'})
        for i in data_author:
            author_name = i.text.strip("\n").strip()
            my_author_name_list.append(author_name)

        # 得到source_list
        data_source = tbody.find_all('td', attrs={'class': 'source'})
        for i in data_source:
            source = i.text.strip("\n").strip()
            if source:
                my_source_list.append(source)
            else:
                my_source_list.append("无source")

        # 得到publication_time_list
        data_publication_time = tbody.find_all('td', attrs={'class': 'date'})
        for i in data_publication_time:
            publication_time = i.text.strip("\n").strip()
            if publication_time:
                my_publication_time_list.append(publication_time)
            else:
                my_publication_time_list.append("无publication_time")
        # 得到quote_list
        data_quote = tbody.find_all('td', attrs={'class': 'quote'})
        for i in data_quote:
            quote = i.text.strip("\n").strip()
            if quote:
                my_quote_list.append(quote)
            else:
                my_quote_list.append("0")
        # 得到database_list
        data_database = tbody.find_all('td', attrs={'class': 'data'})
        for i in data_database:
            database = i.text.strip("\n").strip()
            if database:
                my_database_list.append(database)
            else:
                my_database_list.append("无database")

        time.sleep(1)
        return my_url_list, my_title_list, my_author_name_list, my_source_list, my_publication_time_list, my_quote_list, my_database_list
    except Exception as e:
        print(f'在当前页面爬取论文url时出现了错误{e}')
        return my_url_list, my_title_list, my_author_name_list, my_source_list, my_publication_time_list, my_quote_list, my_database_list


# 翻页
def change_page(my_driver):
    # 随机选择点击翻页或键盘右键操作(*模拟人类操作)
    random_choice = random.choice([0, 1])  # 0 代表 click(),1 代表 send_keys(Keys.ARROW_RIGHT)
    if random_choice == 0:
        # 防止翻到最后一页找不到'#Page_next_top'元素报错
        try:
            element = my_driver.find_element(By.CSS_SELECTOR, '#Page_next_top')
            element.click()
        except:
            my_driver.find_element(By.TAG_NAME, 'body').send_keys(Keys.ARROW_RIGHT)
    else:
        my_driver.find_element(By.TAG_NAME, 'body').send_keys(Keys.ARROW_RIGHT)

    # 随机时间翻页: 生成一个随机的时间间隔,范围在1到5秒之间
    my_random_interval = random.uniform(1, 5)
    time.sleep(my_random_interval)  # 随机暂停程序的执行

    # 获取翻页后页面soup信息
    content = my_driver.page_source.encode('utf-8')
    my_soup = BeautifulSoup(content, 'html.parser')
    return my_soup


# 获取当前页数和总页数
def get_pn(my_soup):
    try:
        # 判断是否需要翻页
        if my_soup.find_all('span', attrs={'class': 'countPageMark'}):
            my_pn = my_soup.find_all('span', attrs={'class': 'countPageMark'})[0].text
            my_current_pn = int(my_pn.split("/")[0])  # 当前页数
            my_total_pn = int(my_pn.split("/")[1])  # 总页数
        else:
            my_current_pn = 1
            my_total_pn = 1
    except Exception as e:
        print(f'获取当前页数和总页数时出错,错误为:{e},为防止程序中断,设置总页数为1,不进行翻页')
        my_current_pn = 1
        my_total_pn = 1
    return my_current_pn, my_total_pn


# 爬取论文页面详细信息
def get_paper(my_url_list: list) -> list[Paper]:
    paper_list = []
    # 尝试爬取论文页面
    i = 0
    for my_url in my_url_list:
        i += 1
        print(f"开始爬取第{i}个论文页面")
        try:
            headers = {
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 Edg/118.0.2088.46",
                "Cache-Control": "no-cache",  # 禁用缓存
                "Pragma": "no-cache"
            }
            response = requests.get(my_url, headers=headers)
            html = response.text
            my_soup = BeautifulSoup(html, "html.parser")

            # 论文名
            title_list = my_soup.findAll("h1")
            if title_list:
                my_title = title_list[0].text
            else:
                my_title = "未找到标题信息"

            # 作者姓名
            "{'张三': ['1'], '李四': ['1', '2', '3'], '王五': ['1']}"
            author_data: dict[str, list[str]] = {}

            h3 = my_soup.find_all("h3")[0]
            spans = BeautifulSoup(str(h3), 'html.parser')
            spans = spans.find_all('span')
            # span标签存储作者姓名和可能存在的标签里的数字
            for span_a in spans:
                # 使用正则表达式提取作者姓名 ##示例:"张三1,", "李四1,2,3", “王五”
                author_name = re.match(r'([^0-9 ,]+)', span_a.text).group(0)
                # 判断是否含有上标文本<sup>,从而判断是否有多个单位
                if span_a.find("sup", recursive=False):  # 在当前a标签中,不递归查找,包含所有sup子标签的列表
                    # 提取作者姓名对应的数字
                    author_number = span_a.find("sup").text  # "1", "1,2,3"
                    number_list = []
                    # 判断是否同时对应多个单位
                    if "," in author_number:
                        number_list = author_number.split(",")
                    else:
                        number_list.append(author_number)
                    # 将姓名与数字按字典嵌套列表保存
                    author_data[author_name] = number_list
                else:
                    author_data[author_name] = ['1']

            # 作者单位
            "{'1': 'a研究所'}; {'1': 'a大学', '2', 'x研究所'}; {'1', '未查找到单位'}"
            unit_data: dict[str, str] = {}  #
            units = my_soup.select("h3 a.author")  # 所有包含作者的a.author标签
            # 通过if判断作者单位是否存在
            if units:
                for unit in units:
                    #  判断是否有多个单位
                    if "." in unit.text:  # ”1. 国防大学政治学院“
                        key = str(unit.text.split(".")[0])  # 数字
                        value = unit.text.split(".")[1]  # 单位
                        unit_data[key] = value
                    else:
                        unit_data["1"] = unit.text  # ”辽宁大学”
            else:  # units = []论文详细页面未显示单位
                unit_data["1"] = ""  # 未查找到单位

            # 将作者与单位整合成一个字典
            """
            {'张三': ['a研究所']}
            {'李四': ['a大学', '辽宁大学', 'b研究所'], '张三', ['b研究所']}
            {'王五', ['未查找到单位']}
            """
            author_unit: dict[str, list[str]] = {}
            for name, numbers in author_data.items():  # 返回一个由键值对组成的元组
                units = []
                for number in numbers:
                    unit = unit_data.get(number)
                    units.append(unit)
                author_unit[name] = units

            # 摘要
            abstract_list = my_soup.findAll("span", attrs={"class": "abstract-text"})
            if abstract_list:
                abstract = abstract_list[0]
                abstract_text = abstract.text
            else:
                abstract_text = "未找到摘要信息"

            # 关键字
            keywords_data = []
            keywords = my_soup.findAll("a", attrs={"name": "keyword"})
            for keyword in keywords:
                key_word = keyword.text.strip()
                keywords_data.append(key_word.strip(";"))

            # 期刊名和时间|会议名和会议时间
            name = ""
            time = ""
            if len(my_soup.select("div.top-tip span a")) == 2:
                name = "期刊名:" + my_soup.select("div.top-tip span a")[0].text  # 期刊名
                time = "期刊日期:" + my_soup.select("div.top-tip span a")[1].text  # 日期
            elif len(my_soup.select("div.top-tip span a")) == 1:
                "只有期刊名没有日期"
                name = "期刊名:" + my_soup.select("div.top-tip span a")[0].text  # 期刊名
                time = "无期刊日期"  # 日期
            elif len(my_soup.select("div.top-tip span a")) == 0:
                "没有期刊名和日期"
                # 判断是否有会议名称
                if my_soup.find_all('span', attrs={"class": "rowtit"}, string='会议名称:'):
                    row_name = my_soup.find_all('span', attrs={"class": "rowtit"}, string='会议名称:')[0]
                    name = "会议名称:" + row_name.find_next_sibling('p').text  # 会议名称
                else:
                    name = "未查找到会议名称"
                # 判断是否有会议时间
                if my_soup.find_all('span', attrs={"class": "rowtit"}, string='会议时间:'):
                    row_time = my_soup.find_all('span', attrs={"class": "rowtit"}, string='会议时间:')[0]
                    time = "会议时间:" + row_time.find_next_sibling('p').text  # 会议时间
                else:
                    time = "未查找到会议时间"

            # 文章目录
            cont = my_soup.find_all("h5", string='文章目录')
            contents = my_soup.select('ul.catalog-list li')
            # 判断是否存在目录
            if cont:
                "存在目录"
                content_string = ""
                for content in contents:
                    content_string += content['title'] + "\n"
            else:
                content_string = "无目录"

            # 保存爬取的信息到Paper类中
            paper = Paper(
                my_title, author_data, unit_data, author_unit, abstract_text, keywords_data, name, time, content_string
            )
            paper_list.append(paper)

        except Exception as e:
            print(f"爬取第{i}个论文页面{my_url}时出现错误了 {e}")

    return paper_list


# 创建MySQL关键词对应的数据表格
def create_table(keyword: str):
    # 使用关键字创建表名
    table_name = keyword

    # 创建表的SQL语句
    create_table_sql = f"""
        CREATE TABLE IF NOT EXISTS {table_name} (
            论文标题 varchar(100) PRIMARY KEY,
            作者姓名 varchar(50),
            作者单位 varchar(100),
            摘要 Text,
            关键字 varchar(100),
            期刊名或会议名称 varchar(30),
            日期或会议时间 varchar(30),
            目录 Text
        )
    """
    cursor.execute(create_table_sql)


# 在MySQL关键词对应的表格中插入数据
def insert_sql(keyword: str, Paper: Paper, i: int):
    authors = ', '.join(Paper.author_unit.keys())  # 作者姓名
    units = ', '.join([value[0] for value in Paper.author_unit.values() if value and value[0]])  # 作者单位
    keywords = ', '.join(Paper.keywords_data)  # 关键词

    # 处理可能为空的字段
    if units == "":
        units = "无单位"
    if Paper.abstract_text is None:
        Paper.abstract_text = "无摘要"
    if not keywords:
        keywords = "无关键词"
    if Paper.name is None:
        Paper.name = "无期刊名或会议名称"
    if Paper.time is None:
        Paper.time = "无日期或会议时间"
    if Paper.content_string is None:
        Paper.content_string = "无目录"

    # 构建插入数据的SQL语句
    insert_table_sql = f"""
        INSERT IGNORE INTO {keyword} (论文标题, 作者姓名, 作者单位, 摘要, 关键字, 期刊名或会议名称, 日期或会议时间, 目录)
        VALUES (%s, %s, %s, %s, %s, %s, %s, %s);
    """

    # 执行插入操作
    print(f"插入第{i}个数据")
    cursor.execute(insert_table_sql, (
        Paper.title, authors, units, Paper.abstract_text, keywords, Paper.name, Paper.time, Paper.content_string
    ))


if __name__ == '__main__':
    path = "E:\python_learn\Python爬虫\demo\爬取代码\测试关键词.txt"
    keyword_list = get_keyword_list(path)
    print(keyword_list)

    "爬取每个关键字对应的所有论文页面详细信息,并保存到MySQL中"
    # 构建到MySQL数据库的链接
    conn = Connection(
        host='localhost',  # 主机名(或IP地址)
        port=3306,  # 端口,默认3306
        user='root',  # 账户
        password='123456',  # 密码
        autocommit=True  # 设置自动提交
    )
    # 获取游标对象
    cursor = conn.cursor()
    conn.select_db("知网论文信息")  # 选择数据库

    # 爬取主体语句
    for my_keyword in keyword_list:
        url_list = []  # 论文链接
        title_list = []  # 论文名
        author_name_list = []  # 作者姓名
        source_list = []  # 来源
        publication_time_list = []  # 发表时间
        quote_list = []  # 被引
        database_list = []  # 数据库
        try:
            # 1. 打开知网
            driver = webdriver.Edge()
            # 输入关键词并调整页面显示情况
            soup = driver_open(driver, my_keyword)

            # 2. 爬取信息>>保存信息>>翻页>>爬取信息>>翻页>>......保存信息到列表
            current_pn = get_pn(soup)[0]  # 当前页数
            total_pn = get_pn(soup)[1]  # 总页数
            # 判断是否只有一页
            if current_pn == total_pn:  # 只有一页
                # 开始爬取当前页面
                spider_url(
                    soup,
                    url_list,
                    title_list,
                    author_name_list,
                    source_list,
                    publication_time_list,
                    quote_list,
                    database_list
                )
            else:
                for pn in range(current_pn, total_pn):
                    # 爬取
                    spider_url(
                        soup,
                        url_list,
                        title_list,
                        author_name_list,
                        source_list,
                        publication_time_list,
                        quote_list,
                        database_list
                    )
                    # 随机暂停程序的执行
                    time.sleep(random.uniform(1, 5))
                    # 翻页
                    soup = change_page(driver)  # 自动跳转到下一页
                    # 最后一页爬取
                    spider_url(
                        soup,
                        url_list,
                        title_list,
                        author_name_list,
                        source_list,
                        publication_time_list,
                        quote_list,
                        database_list
                    )
            # 关闭知网
            driver.close()

            # 判断爬取的数据是否错位,如果全部相同则没有错误
            url_len = len(url_list)
            title_len = len(title_list)
            author_name_len = len(author_name_list)
            source_len = len(source_list)
            publication_time_len = len(publication_time_list)
            quote_len = len(quote_list)
            database_len = len(database_list)
            print("数据列表长度(如果全部相同则没有错误)", url_len, title_len, author_name_len, source_len,
                  publication_time_len, quote_len, database_len)

            # 3. 保存数据
            # 保存url_list到本地
            try:
                # 保存url到本路径下的文件夹"E:\python_learn\Python爬虫\demo\爬取到的文件\每个关键词对应论文的url"
                path_url = f'E:\python_learn\Python爬虫\demo\爬取到的文件\每个关键词对应论文的url\\{my_keyword}.txt'
                f = open(path_url, 'w', encoding='utf-8')
                # 写入文件
                for url in url_list:
                    f.write(url)
                f.flush()
                f.close()
            except Exception as e:
                print(f"{my_keyword}下写入url文件时时出现错误了{e}")
            # 保存到csv文件中
            path_csv = f'E:\python_learn\Python爬虫\demo\爬取到的文件\每个关键词对应csv表格\\{my_keyword}.csv'
            try:
                # 检查文件是否存在
                if not os.path.exists(path_csv):
                    # 创建文件
                    f = open(path_csv, 'a', encoding='utf8', newline='')
                    column = ['论文名', '作者姓名', '来源', '发表时间', '被引', '数据库', 'url']  # 列表头名称
                    writer = csv.writer(f)
                    writer.writerow(column)
                    i = 0
                    # 一个sheet
                    for title in title_list:
                        rows = [(title_list[i], author_name_list[i], source_list[i], publication_time_list[i],
                                 quote_list[i], database_list[i], url_list[i])]
                        i += 1
                        writer.writerows(rows)
                    f.flush()
                    f.close()
            except Exception as e:
                csv_list = [title_list, author_name_list, source_list, publication_time_list, quote_list, database_list]
                print(f"{my_keyword}下写入csv文件时时出现错误了{e}")

        except Exception as e:
            print(f'在爬取关键词{my_keyword}下的所有论文url信息时出现了错误{e}')

        # 爬取论文页面详细信息
        try:
            # 爬取每一个关键词下论文页面详细信息,创建包含所有论文的Paper类的列表
            Pager_list: list[Paper] = get_paper(url_list)
            # 创建关键词对应的MySQL数据表格
            create_table(my_keyword)
            # 插入数据
            i = 1
            for pager in Pager_list:
                insert_sql(my_keyword, pager, i)
                i += 1

        except Exception as e:
            print(f'在爬取关键词{my_keyword}下的所有论文页面详细信息,并写入MySQL时出现了错误{e}')

    # 关闭MySQL链接
    cursor.close()
    conn.close()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值