Beautiful Soup是一个Python库,它将HTML或XML文档解析为树结构,以便于从中查找和提取数据。它通常用于从网站上抓取数据。
Beautiful Soup具有简单的Pythonic界面和自动编码转换功能,可以轻松处理网站数据。
网页是结构化文档,Beaut是一个Python库,它将HTML或XML文档解析为树结构,以便于查找和提取数据。在本指南中,您将编写一个Python脚本,可以通过Craigslist获得摩托车价格。脚本将被设置为使用cron作业定期运行,生成的数据将导出到Excel电子表格中进行趋势分析。通过替换不同的url并相应地调整脚本,您可以轻松地将这些步骤适应于其他网站或搜索查询。
python --version
sudo apt update && sudo apt upgrade
pip install beautifulsoup4
pip install tinydb urllib3 xlsxwriter lxml
bs4
中的BeautifulSoup
类将处理web页面的解析。datetime
模块用于处理日期。Tinydb
为NoSQL
数据库提供了一个API, urllib3
模块用于发出http请求。最后,使用xlsxwriter
API创建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解析器已经足够了,但是根据您的需要,您可能需要检查官方文件中描述的其他选项。
类的对象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>
results = soup.find_all("li", class_="result-row")
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()
}
'pid': result'data-pid'
datetime
的数据属性,该time
元素是作为其子元素的p
标记的子元素result
。要访问此值,请使用以下格式:'date': result.p.time'datetime'
string
方法:<span class="result-price">$12791</span>
可以访问:
'cost': clean\_money(result.a.span.string.strip())
这里的值通过使用Python strip()
函数以及clean_money
删除美元符号的自定义函数进一步处理。
clean_pic
用于将第一张图片的URL分配给pic:'pic': clean_pic(result.a'data-ids')
'createdt': datetime.datetime.now().isoformat()
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 |
---|
这是两个简短的自定义函数,用于清理代码段数据。该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。
该make_excel
函数获取数据库中的数据并将其写入Excel电子表格。
craigslist.py
Headlines = "Pid", "Date", "Cost", "Webpage", "Pic", "Desc", "Created Date"
row = 0
该标题变量是冠军在电子表格中列的列表。该行变量跟踪当前电子表格行。
craigslist.py1 2
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
前两项在set_column
方法中始终相同。这是因为它正在设置从第一个指示列到下一个列的一部分列的属性。最后一个值是以字符为单位的列的宽度。
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。这使得生成的链接可在最终电子表格中单击。
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任务,以定期自动运行抓取脚本。数据
ssh normaluser@<Linode Public IP>
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)
crontab -e
此示例条目将每天早上6:30运行python程序。
30 6 * * * /usr/bin/python3 /home/normaluser/craigslist.py
python程序将编写motorcycle.xlsx
电子表格/home/normaluser/
。
在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