如何用Beautiful Soup爬取一个网址

什么是Beautiful Soup?

Beautiful Soup是一个Python库,它将HTML或XML文档解析为树结构,以便于从中查找和提取数据。它通常用于从网站上抓取数据。

Beautiful Soup具有简单的Pythonic界面和自动编码转换功能,可以轻松处理网站数据。

网页是结构化文档,Beaut是一个Python库,它将HTML或XML文档解析为树结构,以便于查找和提取数据。在本指南中,您将编写一个Python脚本,可以通过Craigslist获得摩托车价格。脚本将被设置为使用cron作业定期运行,生成的数据将导出到Excel电子表格中进行趋势分析。通过替换不同的url并相应地调整脚本,您可以轻松地将这些步骤适应于其他网站或搜索查询。

安装Beautiful Soup

安装Python

  1. 下载并安装Miniconda:curl -OL https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86\_64.sh bash Miniconda3-latest-Linux-x86\_64.sh
  2. 在安装过程中,系统会多次提示您,查看条款和条件,您在每个提示框选择“是”即可。
  3. 重新启动shell会话以使PATH的更改生效。
  4. 检查你的Python版本:

python --version

安装美丽的汤和依赖

  1. 更新您的系统:

sudo apt update && sudo apt upgrade

  1. 使用pip安装最新版本的Beautiful Soup:

pip install beautifulsoup4

  1. 安装依赖项:

pip install tinydb urllib3 xlsxwriter lxml

构建Web Scraper

必需的模块

bs4中的BeautifulSoup类将处理web页面的解析。datetime模块用于处理日期。TinydbNoSQL数据库提供了一个API, urllib3模块用于发出http请求。最后,使用xlsxwriterAPI创建excel电子表格。

craigslist.py在文本编辑器中打开并添加必要的import语句:

craigslist.py

1 2 3 4 5

from bs4 import BeautifulSoup import datetime from tinydb import TinyDB, Query import urllib3 import xlsxwriter

添加全局变量

在import语句之后,添加全局变量和配置选项:

craigslist.py

1 2 3 4

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) url = 'https://elpaso.craigslist.org/search/mcy?sort=date' total_added = 0

url存储要抓取的网页的URL,并total_added用于跟踪添加到数据库的结果总数。该urllib3.disable_warnings()函数忽略任何SSL证书警告。

检索网页

make_soup函数向目标url发出GET请求,并将生成的HTML转换为BeautifulSoup对象:

craigslist.py

1 2 3 4

def make_soup(url): http = urllib3.PoolManager() r = http.request("GET", url) return BeautifulSoup(r.data,'lxml')

urllib3库具有出色的异常处理能力; 如果make_soup抛出任何错误,请查看urllib3文档以获取详细信息。

Beautiful Soup有不同的解析器,对网页的结构或多或少有些严格。对于本指南中的示例脚本,lxml解析器已经足够了,但是根据您的需要,您可能需要检查官方文件中描述的其他选项。

处理Soup对象

类的对象BeautifulSoup以树为结构组织。要访问您感兴趣的数据,您必须熟悉原始HTML文档中数据的组织方式。在浏览器中转到初始网站,右键单击并选择查看页面源(或检查,具体取决于您的浏览器),以查看您要抓取的数据的结构:

https://elpaso.craigslist.org/search/mcy?sort=date

<li class="result-row" data-pid="6370204467">
  <a href="https://elpaso.craigslist.org/mcy/d/ducati-diavel-dark/6370204467.html" class="result-image gallery" data-ids="1:01010_8u6vKIPXEsM,1:00y0y_4pg3Rxry2Lj,1:00F0F_2mAXBoBiuTS">
    <span class="result-price">$12791</span>
  </a>
  <p class="result-info">
    <span class="icon icon-star" role="button">
    <span class="screen-reader-text">favorite this post</span>
    </span>
    <time class="result-date" datetime="2017-11-01 19:38" title="Wed 01 Nov 07:38:13 PM">Nov  1</time>
    <a href="https://elpaso.craigslist.org/mcy/d/ducati-diavel-dark/6370204467.html" data-id="6370204467" class="result-title hdrlnk">Ducati Diavel | Dark</a>
    <span class="result-meta">
            <span class="result-price">$12791</span>
            <span class="result-tags">
            pic
            <span class="maptag" data-pid="6370204467">map</span>
            </span>
            <span class="banish icon icon-trash" role="button">
            <span class="screen-reader-text">hide this posting</span>
            </span>
    <span class="unbanish icon icon-trash red" role="button" aria-hidden="true"></span>
    <a href="#" class="restore-link">
            <span class="restore-narrow-text">restore</span>
            <span class="restore-wide-text">restore this posting</span>
    </a>
    </span>
  </p>
</li>
  1. 通过仅选择li html标签来选择网页代码段,并通过仅选择具有结果类类别的li标签来进一步缩小选项范围。该结果变量包含所有符合该条件的网页片段:
results = soup.find_all("li", class_="result-row")
  1. 尝试根据目标片段的结构创建记录。如果结构不匹配,那么Python将抛出异常,这将导致它跳过此记录和片段:
craigslist.py
rec = {
'pid': result['data-pid'],
'date': result.p.time['datetime'],
'cost': clean_money(result.a.span.string.strip()),
'webpage': result.a['href'],
'pic': clean_pic(result.a['data-ids']),
'descr': result.p.a.string.strip(),
'createdt': datetime.datetime.now().isoformat()
}
  1. 使用Beautiful Soup的数组表示法来访问HTML元素的属性:
'pid': result'data-pid'
  1. 其他数据属性可以在HTML结构中更深地嵌套,并且可以使用点和数组表示法的组合来访问。例如,发布结果的日期存储在元素中,该元素是元素datetime的数据属性,该time元素是作为其子元素的p标记的子元素result。要访问此值,请使用以下格式:
'date': result.p.time'datetime'
  1. 有时所需的信息是标签内容(在开始和结束标签之间)。要访问标记内容,BeautifulSoup提供了以下string方法:

<span class="result-price">$12791</span>

可以访问:

'cost': clean\_money(result.a.span.string.strip())

这里的值通过使用Python strip()函数以及clean_money删除美元符号的自定义函数进一步处理。

  1. Craigslist上出售的大多数商品都包含该商品的图片。自定义函数clean_pic用于将第一张图片的URL分配给pic
'pic': clean_pic(result.a'data-ids')
  1. 元数据可以添加到记录中。例如,您可以添加一个字段来跟踪创建特定记录的时间:
'createdt': datetime.datetime.now().isoformat()
  1. 在插入记录之前,使用Query对象检查数据库中是否已存在记录。这可以避免创建重复记录。
craigslist.py
Result = Query()
s1 = db.search(Result.pid == rec["pid"])

if not s1:
    total_added += 1
    print ("Adding ... ", total_added)
    db.insert(rec)

错误处理

处理两种类型的错误很重要。这些不是脚本中的错误,而是片段结构中的错误导致Beautiful Soup的API抛出错误。

一个AttributeError当点符号没有找到兄弟标签当前HTML标记将被抛出。例如,如果特定代码段没有锚标记,那么代价键将抛出错误,因为它会横向并因此需要锚标记。

另一个错误是KeyError。如果缺少必需的HTML标记属性,则会抛出它。例如,如果代码段中没有data-pid属性,则pid键将引发错误。

如果在解析结果时发生这些错误中的任何一个,则将跳过该结果以确保未将错误的片段插入到数据库中:

craigslist.py

1 2

except (AttributeError, KeyError) as ex: pass

清洁功能(Cleaning Functions)

这是两个简短的自定义函数,用于清理代码段数据。该clean_money函数从输入中删除任何美元符号:

craigslist.py

1 2

def clean_money(amt): return int(amt.replace("$",""))

clean_pic函数生成一个URL,用于访问每个搜索结果中的第一个图像:

craigslist.py

1 2 3 4 5

def clean_pic(ids): idlist = ids.split(",") first = idlist0 code = first.replace("1:","") return "https://images.craigslist.org/%s_300x300.jpg" % code

该函数提取并清除第一个图像的id,然后将其添加到基本URL。

将数据写入Excel电子表格

make_excel函数获取数据库中的数据并将其写入Excel电子表格。

  1. 添加电子表格变量:
craigslist.py
Headlines = "Pid", "Date", "Cost", "Webpage", "Pic", "Desc", "Created Date" 
row = 0
该标题变量是冠军在电子表格中列的列表。该行变量跟踪当前电子表格行。
  1. 使用xlswriter打开工作簿,并添加一个工作表来接收数据。
craigslist.py1 2
workbook = xlsxwriter.Workbook('motorcycle.xlsx') 
worksheet = workbook.add_worksheet()
  1. 准备工作表: craigslist.py
worksheet.set_column(0,0, 15) # pid
worksheet.set_column(1,1, 20) # date
worksheet.set_column(2,2, 7)  # cost
worksheet.set_column(3,3, 10)  # webpage
worksheet.set_column(4,4, 7)  # picture
worksheet.set_column(5,5, 60)  # Description
worksheet.set_column(6,6, 30)  # created date

前两项在set_column方法中始终相同。这是因为它正在设置从第一个指示列到下一个列的一部分列的属性。最后一个值是以字符为单位的列的宽度。

  1. 将列标题写入工作表: craigslist.py1 2 for col, title in enumerate(Headlines): worksheet.write(row, col, title)
  2. 将记录写入数据库:
craigslist.py
for item in db.all():
    row += 1
    worksheet.write(row, 0, item['pid'] )
    worksheet.write(row, 1, item['date'] )
    worksheet.write(row, 2, item['cost'] )
    worksheet.write_url(row, 3, item['webpage'], string='Web Page')
    worksheet.write_url(row, 4, item['pic'], string="Picture" )
    worksheet.write(row, 5, item['descr'] )
    worksheet.write(row, 6, item['createdt'] )

每行中的大多数字段都可以使用worksheet.write; worksheet.write_url用于列表和图像URL。这使得生成的链接可在最终电子表格中单击。

  1. 关闭Excel工作簿:
craigslist.py
    workbook.close()

主要常规

主例程将遍历搜索结果的每一页,并在每个页面上运行soup_process函数。它还跟踪全局变量total_added中添加的数据库条目总数,该变量在soup_process函数中更新,并在完成scrape后显示。最后,它创建了一个TinyDB数据库db.json并存储解析后的数据; 当scrape完成时,数据库将传递给make_excel函数以写入电子表格。

craigslist.py

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

def main(url): total_added = 0 db = TinyDB("db.json") while url: print ("Web Page: ", url) soup = soup_process(url, db) nextlink = soup.find("link", rel="next") url = False if (nextlink): url = nextlink'href' print ("Added ",total_added) make_excel(db)

示例运行可能如下所示。请注意,每个页面都在URL中嵌入了索引。这就是Craigslist如何知道下一页数据的开始位置:

$ python3 craigslist.py
Web Page:  https://elpaso.craigslist.org/search/mcy?sort=date
Adding ...  1
Adding ...  2
Adding ...  3
Web Page:  https://elpaso.craigslist.org/search/mcy?s=120&sort=date
Web Page:  https://elpaso.craigslist.org/search/mcy?s=240&sort=date
Web Page:  https://elpaso.craigslist.org/search/mcy?s=360&sort=date
Web Page:  https://elpaso.craigslist.org/search/mcy?s=480&sort=date
Web Page:  https://elpaso.craigslist.org/search/mcy?s=600&sort=date
Added  3

设置Cron自动

本节将设置一个cron任务,以定期自动运行抓取脚本。数据

  1. 以普通用户身份登录您的计算机:
ssh normaluser@<Linode Public IP>
  1. 确保完整craigslist.py脚本位于主目录中: craigslist.py
from bs4 import BeautifulSoup
import datetime
from tinydb import TinyDB, Query
import urllib3
import xlsxwriter

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

url = 'https://elpaso.craigslist.org/search/mcy?sort=date'
total_added = 0

def make_soup(url):
    http = urllib3.PoolManager()
    r = http.request("GET", url)
    return BeautifulSoup(r.data,'lxml')

def main(url):
    global total_added
    db = TinyDB("db.json")

    while url:
        print ("Web Page: ", url)
        soup = soup_process(url, db)
        nextlink = soup.find("link", rel="next")

        url = False
        if (nextlink):
            url = nextlink['href']

    print ("Added ",total_added)

    make_excel(db)

def soup_process(url, db):
    global total_added

    soup = make_soup(url)
    results = soup.find_all("li", class_="result-row")

    for result in results:
        try:
            rec = {
                'pid': result['data-pid'],
                'date': result.p.time['datetime'],
                'cost': clean_money(result.a.span.string.strip()),
                'webpage': result.a['href'],
                'pic': clean_pic(result.a['data-ids']),
                'descr': result.p.a.string.strip(),
                'createdt': datetime.datetime.now().isoformat()
            }

            Result = Query()
            s1 = db.search(Result.pid == rec["pid"])

            if not s1:
                total_added += 1
                print ("Adding ... ", total_added)
                db.insert(rec)

        except (AttributeError, KeyError) as ex:
            pass

    return soup

def clean_money(amt):
    return int(amt.replace("$",""))

def clean_pic(ids):
    idlist = ids.split(",")
    first = idlist[0]
    code = first.replace("1:","")
    return "https://images.craigslist.org/%s_300x300.jpg" % code

def make_excel(db):
    Headlines = ["Pid", "Date", "Cost", "Webpage", "Pic", "Desc", "Created Date"]
    row = 0

    workbook = xlsxwriter.Workbook('motorcycle.xlsx')
    worksheet = workbook.add_worksheet()

    worksheet.set_column(0,0, 15) # pid
    worksheet.set_column(1,1, 20) # date
    worksheet.set_column(2,2, 7)  # cost
    worksheet.set_column(3,3, 10)  # webpage
    worksheet.set_column(4,4, 7)  # picture
    worksheet.set_column(5,5, 60)  # Description
    worksheet.set_column(6,6, 30)  # created date

    for col, title in enumerate(Headlines):
        worksheet.write(row, col, title)

    for item in db.all():
        row += 1
        worksheet.write(row, 0, item['pid'] )
        worksheet.write(row, 1, item['date'] )
        worksheet.write(row, 2, item['cost'] )
        worksheet.write_url(row, 3, item['webpage'], string='Web Page')
        worksheet.write_url(row, 4, item['pic'], string="Picture" )
        worksheet.write(row, 5, item['descr'] )
        worksheet.write(row, 6, item['createdt'] )

    workbook.close()

main(url)

  1. 以用户身份添加cron选项卡条目:
crontab -e

此示例条目将每天早上6:30运行python程序。

30 6 * * * /usr/bin/python3 /home/normaluser/craigslist.py

python程序将编写motorcycle.xlsx电子表格/home/normaluser/

检索Excel报告

在Linux上

使用scp motorcycle.xlsx从运行python程序的远程计算机复制到此计算机:

scp normaluser@<Linode Public IP>:/home/normaluser/motorcycle.xlsx .

在Windows上

使用Firefox的内置sftp功能。在地址栏中键入以下URL,它将请求密码。从显示的目录列表中选择电子表格。

sftp://normaluser@<Linode Public IP>/home/normaluser

本文的版权归 阿小庆 所有,如需转载请联系作者。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Spark学习技巧

Kafka源码系列之分组消费的再平衡策略

一,Kafka消费模式 从kafka消费消息,kafka客户端提供两种模式: 分区消费,分组消费。 分区消费对应的就是我们的DirectKafkaInputDS...

1K6
来自专栏生信宝典

Linux学习 - 文件内容操作(1)

Linux下文件内容操作 常用的文件内容操作有文件压缩解压缩、文件大小行数统计、文件内容查询等。 gzip: 压缩文件; gunzip: 解压缩文件 # gzi...

2275
来自专栏生信技能树

在R里面玩转vcf文件

其中meta存储着vcf的头文件,而fix存储在vcf的固定列,gt存储在样本基因型信息。

2253
来自专栏Java技术栈

非常有用的并发控制-循环栅栏CyclicBarrier

昨天我讲了倒计时器CountDownLatch的应用,它是阻塞线程直到计时器归0的一种等待方式。今天讲的这个循环栅栏CyclicBarrier与倒计时器非常类似...

39512
来自专栏linux驱动个人学习

Android图形显示之硬件抽象层Gralloc【转】

3335
来自专栏進无尽的文章

关于-#pragma

在所有的预处理指令中,#Pragma 指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。#pragma指令对每个编译器给出了一...

2301
来自专栏企鹅号快讯

盘点开发者最爱的 IntelliJ 插件 Top 10

关键时刻,第一时间送达! IntelliJ的十大插件?相信每个人都有自己的选择。我们也同样如此。在这里,我们为您带来我们认为的十大IntelliJ插件。 如果你...

1957
来自专栏信安之路

PE 病毒与 msf 奇遇记

通俗的讲,PE 病毒就是感染 PE 文件的病毒,通过修改可执行文件的代码中程序入口地址,变为恶意代码的的入口,导致程序运行时执行恶意代码。

1110
来自专栏逆向技术

16位汇编第三讲 分段存储管理思想

      内存分段 一丶分段(汇编指令分段) 1.为什么分段?   因为分段是为了更好的管理数据和代码,就好比C语言为什么会有内存4区一样,否则汇编代码都写...

2116
来自专栏码农分享

对LinqtoExcel的扩展 【数据有限性,逻辑有效性】

接着上文的内容继续讲,上文中我提到了对Excel操作帮助类库LinqToExcel类库的优缺点和使用方法。我也讲到了自己在使用中碰到的问题,我也开发了一个简单的...

1858

扫码关注云+社区

领取腾讯云代金券