使用爬虫爬取百度搜索结果及各网站正文(request库、selenium库和beautifulsoup库)


任务:

  1. 给定搜索词,获取百度搜索结果
  2. 根据各项结果获取对应网站正文部分

获取网站源代码

header的定义

header = {
    "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", 
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", 
    "Accept-Encoding": "gzip, deflate, br", 
    "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
    "Connection": "keep-alive", 
    "Host": "www.baidu.com", 
    # 一般cookie要常换
    "Cookie": "BIDUPSID=B74BEE28F591253CB775FB0142D52FC2; PSTM=1689585249; BAIDUID=B74BEE28F591253CF56870F22EFCC449:FG=1; BAIDUID_BFESS=B74BEE28F591253CF56870F22EFCC449:FG=1; COOKIE_SESSION=34928_1_6_7_7_3_1_0_6_2_0_0_34832_0_4_0_1694389091_1690388333_1694389087%7C9%23735794_9_1690388331%7C6; BAIDU_WISE_UID=wapp_1694430439254_663; ZFY=Bk0QHaMv6sWqRVWER3GYjzk0SUaJaWd4P20GIQMqBrw:C; BD_UPN=12314753; BA_HECTOR=008l210l04al0la180ag0k041ij5a1k1o; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; BD_CK_SAM=1; PSINO=2; H_PS_PSSID=39318_38831_39399_39396_39419_39535_39438_39540_39497_39234_39466_26350_39422; delPer=0; baikeVisitId=cdc30dd1-51a7-4620-beec-d2f7c9f0a1f5; H_PS_645EC=fd50i1NCbX7z4hb86lJCLoimNedSK%2FqhjkCuMLyGssdVMq7QnjmYEdTuhJU; BDSVRTM=171"
}

header实际为一个字典,为访问百度时提供必要的信息。
一般来讲只需要提供Cookie就可以访问大多数网站,其余可能需要的还有HostUser-Agent

通过request库获取百度搜索结果网站源代码

通过分析百度搜索url可以发现
https://www.baidu.com/s?wd=茅台酒心巧克力&pn=0
前缀https://www.baidu.com/s?是固定的,wd表示搜索词,pn表示搜索结果第几页*10。
所以得出我们的搜索url格式为:

url = "http://www.baidu.com/s?wd=" + key_word + "&pn=" + str(page*10)

接下来调用request库的get函数获取url

r = requests.get(url, headers=header)

r 为返回结果,一般来讲需要查看r.status_code是否等于200来确定网站是否返回正确结果。这里的200和404等等是一组概念。

最后r.text就是html源代码。

下面函数可以根据百度搜索页面结果获取跳转链接及标题简介等内容。

def Creeper(key_word, max_pages=2):
    """
    爬取百度keyword关键词搜索结果
    maxpage: 表示爬取搜索结果页数
    返回: dataframe
    """

    # 分别代表dataframe的列
    page_list = []
    kw_list = []
    title_list = []
    href_list = []
    real_url_list = []
    desc_list = []
    site_list = []
    # 开始爬取
    for page in range(max_pages):
        lg.info("百度搜索:开始爬取第{}页".format(page+1))
        wait_seconds = random.uniform(1, 2)
        lg.info("百度搜索:等待{}秒".format(wait_seconds))
        sleep(wait_seconds)

        url = "http://www.baidu.com/s?wd=" + key_word + "&pn=" + str(page*10)
        r = requests.get(url, headers=header)
        html = r.text
        lg.info("百度搜索:响应码为{}".format(r.status_code))

        # 根据返回的html文本进行解析,过滤出搜索结果
        soup = BeautifulSoup(html, "html.parser")
        result_list_0 = soup.find_all(class_='result c-container new-pmd')
        result_list_1 = soup.find_all(class_='result c-container xpath-log new-pmd')
        result_list = result_list_0 + result_list_1
        lg.info("百度搜索:正在爬取{}, 共查询到{}个结果".format(url, len(result_list)))

        # 对于所有的搜索结果分别获取其标题及简介
        for result in result_list:
            title = result.find('a').text
            # print("title is:", title)
            href = result.find('a')["href"]
            real_url = get_real_url(v_url=href)

            try:
                desc = result.find(class_="content-right_8Zs40").text
            except:
                desc = ""
            try:
                site = result.find(class_="c-color-gray").text
            except:
                site = ""
            # 记录每条结果
            kw_list.append(key_word)
            page_list.append(page + 1)
            title_list.append(title)
            href_list.append(href)
            real_url_list.append(real_url)
            desc_list.append(desc)
            site_list.append(site)
    df = pd.DataFrame(
        {
            "关键词": kw_list,
            "页码": page_list,
            "标题": title_list,
            "简介": desc_list, 
            "百度链接": href_list,
            "真实链接": real_url_list, 
            "网站名称": site_list
        }
    )
    return df

用跳转链接获取真实链接

注意百度搜索的结果都是跳转链接,我们需要获取其真实链接。

def get_real_url(v_url):
    """
    根据跳转链接获取真实连接
    v_url: 跳转链接
    real_url: 真实链接
    """
    r = requests.get(v_url, headers=header, allow_redirects=False)
    if r.status_code == 302:
        real_url = r.headers.get("Location")
    else:
        real_url = re.findall("URL='(.*?)'", r.text)[0]
    return real_url

通过selenium库获取网站源代码

因为百家号等新闻网站的反爬虫确实比较厉害,使用普通的request.get()方法,可能会因为headers参数的设置问题而被认定为爬虫。比起request库,我们还可以真正的调用edge等浏览器以获得网站源代码。

在使用selenium之前,我们需要根据自己的浏览器版本选择对应的驱动。
具体操作方法可以参照这里

驱动实际上是浏览器的调试工具,我认为它更像浏览器的接口。我们在代码中调用浏览器去访问某网站,浏览器将其html源代码返回给我们。

这样一来,我们就真的是用浏览器去访问网站了,反爬虫就识别不出我们来了。不过这种方法比request慢很多很多。

def get_html(site):
    """
    调用selenium获取site的html源码
    """
    s = Service('msedgedriver.exe')
    # 我们并不需要浏览器弹出
    options = Options()
    options.add_argument('--headless')
    options.add_argument('--disable-gpu') 
    # 启动浏览器的无头模式,访问
    driver = webdriver.Edge(options=options)
    driver.set_page_load_timeout(10)
    try:
        driver.get(site)
    except TimeoutException as e:
        lg.error("time out:", e)

    # 获取页面的源代码
    page_source = driver.page_source
    driver.quit()
    return page_source

注意这里设置了浏览器的无头模式,即让其在后台运行而不显示UI。

获取源代码之后利用beautifulsoup解析

上述两种方法(request和selenium)仅仅是通过不同的手段获取网站的html源代码。真正想要从一堆html中获取我们想要的部分还需要beautifulsoup这样的解析工具。

soup = BeautifulSoup(html, "html.parser")

html是字符串类型,表示网站的html源代码。
其实beatifulsoup在这里只需要掌握两个函数:
soup.find_all()soup.find(), 前者返回列表,后者返回对象

下面这个函数是我根据几个常搜到的网站总结的解析方法。

def parser(soup):
    """
    给定html文本对象soup
    返回其正文部分
    """
    # 用于保存html对象
    result_list = []
    # 百家号、搜狐专属--------------------------------------------------------------------------------------------------------
    try:
        bjh = soup.find_all(name='div', attrs={'data-testid':'article'})[0] # 使用能够覆盖正文的tag,只会找到一个,所以直接索引0。
        result_list += bjh.find_all("p")
        result_list += bjh.find_all("span")
    except:
        pass
    try:
        bjh = soup.find_all("article")[0]
        result_list += bjh.find_all("p")
        result_list += bjh.find_all("span")
    except:
        pass
    # 澎湃专属--------------------------------------------------------------------------------------------------------
    try:
        pp = soup.find_all(name="div", attrs={"class": "index_wrapper__L_zqV"})[0]
        result_list += pp.find_all("p")
        result_list += pp.find_all("span")
    except:
        pass
    # 163专属--------------------------------------------------------------------------------------------------------
    try:
        _163 = soup.find_all(name="div", attrs={"class": "post_body"})[0]
        result_list += _163.find_all("p")
    except:
        pass
    # 站长专属--------------------------------------------------------------------------------------------------------
    try:
        zz = soup.find_all(name="div", attrs={"class": "pcontent", "id": "article-content"})[0]
        result_list += zz.find_all("p")
    except:
        pass
    # 腾讯新闻专属--------------------------------------------------------------------------------------------------------
    try:
        tx = soup.find_all(name="div", attrs={"class": "content-article"})[0]
        # pattern = re.compile("^qnt-")
        result_list += tx.find_all(class_="qnt-p")
    except:
        pass 
    # 新浪专属--------------------------------------------------------------------------------------------------------
    try:
        xl = soup.find_all(name="div", attrs={"class": "article", "id": "artibody"})[0]
        result_list += xl.find_all("p")
    except:
        pass 
    # b站专属--------------------------------------------------------------------------------------------------------
    try:
        bz = soup.find_all(name="div", attrs={"class": "article-container"})[0]
        result_list += bz.find_all("p")
    except:
        pass 
    # 凤凰新闻专属--------------------------------------------------------------------------------------------------------
    try:
        fh = soup.find_all(name="div", attrs={"class": "index_main_content_j-HoG"})[0]
        result_list += fh.find_all("p")
    except:
        pass 
    # 若上述皆未找到--------------------------------------------------------------------------------------------------------
    if len(result_list) == 0:
        pattern = re.compile(".*content.*")
        result_list += soup.find_all(pattern)
        pattern = re.compile(".*article.*")
        result_list += soup.find_all(pattern)
    # --------------------------------------------------------------------------------------------------------
    
    if len(result_list) == 0:
        lg.error("解析器:未发现正文部分")
    else:
        lg.info("解析器:共解析到{}个结果".format(len(result_list)))
    # 对于每个html对象,获取其文本并拼接
    res_body = ""
    for res in result_list:
        res_body += res.text
    return res_body

头文件及主函数

import requests
from bs4 import BeautifulSoup
import pandas as pd
from time import sleep
import random
import re
from selenium import webdriver
from selenium.webdriver.edge.options import Options
from selenium.webdriver.edge.service import Service
from selenium.common.exceptions import TimeoutException
from tqdm import tqdm
import logging as lg

log = lg.basicConfig(level=lg.INFO,
                     format="%(asctime)s - %(levelname)s - %(message)s",
                     filename="log.log",
                     filemode="w")
key_word = "茅台酒心巧克力"
df = Creeper(key_word)
site_list = df["真实链接"].tolist()

body_list = []
for url in tqdm(site_list):
    lg.info("正在爬取{}, ".format(url))
    html = get_html(url)
    soup = BeautifulSoup(html, "html.parser")

    res_body = parser(soup)
    body_list.append(res_body)
    
df["body"] = body_list
df.to_csv(key_word + ".csv", encoding="utf_8_sig", index=False)

这里使用了log模块来记录程序的运行日志。
使用方法:
先调用lg.basicConfig()方法设置好日志路径等,
因为此处使用场景较为简单,所以不需要设置多个不同的lg,
日志输出调用

lg.info("balabala")
lg.error("balabala")
lg.warning("balabala")

就可以了。

我们也可以在lg.basicConfig(level=lg.INFO)修改level级别,大于等于level的消息才会输出,这对寻找error非常有帮助。

结果展示

在这里插入图片描述

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值