Created on 2020-02-10
@author 假如我年華正好
環境:Python 3
爬蟲的兩大步驟:
-
發送請求(request),獲取數據(response)
向網址所在的服務器發送請求,即HTPP Request,服務器正常響應後會得到一個Response,其內容便是所要獲取的頁面內容。
使用工具:
requests
模塊 -
解析網頁內容
使用工具:
BeautifulSoup
模塊 —— HTML/XML解析器
分析目標網頁
本文欲爬取 我愛自然語言 網站上的 招聘求職數據,主要有兩個頁面:第一爲職位的列表頁;第二爲職位的詳情頁。示例如下:
在網頁上,單擊右鍵 —> 查看網頁源代碼,可以看到網頁的html源碼,我們通過 request
得到的內容就是這個。
而Beautiful Soup
可以將我的得到的複雜HTML文檔,轉換成一個複雜的樹形結構,樹每個節點都是Python對象。所有對象可以歸納爲4種:Tag
, NavigableString
, BeautifulSoup
,Comment
。
下面對兩個頁面的源碼進行分析:
(1)頁面1:職位的列表頁
先找到我們需要的內容所在部分,即職位列表的源碼部分;
以其中一行爲例,分析源碼的結構:
分析發現:
- 職位列表的行的 tag 的名字爲
div
,其 class 有兩種:class="row"
和calss="row-alt"
; - 職位類型(全職or實習)如何獲取:div —> span —> img[“alt”] (div tag 下的 span tag 下的 img tag 的 alt 屬性值)
- 職位詳情頁鏈接如何獲取:div —> span —> a[“href”]
- 職位發佈公司如何獲取:
- 工作地點如何獲取:
- 發佈時間如何獲取:div —> span.get_text()
下面用代碼演示如何獲取其中的內容:
import requests
from bs4 import BeautifulSoup
# 第一步:發送請求獲取,獲取數據
url = 'http://www.nlpjob.com/jobs'
resp = requests.get(url) # 發送請求
text = resp.content.decode("utf-8") # 解碼
soup = BeautifulSoup(text, "html5lib") # 實例化成 BeautifulSoup 對象
# 第二步:解析內容
# 獲取所有行的 tag,返回爲 bs 對象組成的列表 (對屬性的限制方式有以下兩種)
content = soup.find_all('div', class_="row-alt")
content += soup.find_all('div', attrs={'class': "row"})
# 以其中一行爲例:
cont = content[0]
row_info = cont.find_all('span', class_="row-info")[0]
# 職位類型
job_type = cont.find_all('img')[0]['alt']
print(job_type)
# 詳情鏈接
href = row_info.find_all('a')[0]['href']
print(href)
# 內容標題/摘要
title = row_info.find_all('a')[0].get_text() #.get_text() = .strings
print(title)
# 公司&工作地點
la = row_info.get_text()
print(la)
# 發佈時間
time_posted = cont.find_all('span', class_="time-posted")[0].get_text()
print(time_posted)
Out[9]:全職
http://www.nlpjob.com/job/61399/%e5%9b%be%e6%a3%ae%e6%9c%aa%e6%9d%a5%e7%a7%91%e6%8a%80%e6%9c%89%e9%99%90%e5%85%ac%e5%8f%b8-at-%e5%9b%be%e6%a3%ae%e6%9c%aa%e6%9d%a5%e7%a7%91%e6%8a%80%e6%9c%89%e9%99%90%e5%85%ac%e5%8f%b8/
圖森未來科技有限公司
圖森未來科技有限公司 at 圖森未來科技有限公司, 不限地址
2020-02-07
注意:列表頁的翻頁問題
發現不同頁只是url最後面的 ?p=
不一樣,所以本文采取:寫個循環,直接按規律修改 url ,循環爬取不同頁。
(2)頁面2:職位的詳情頁
詳情頁內容比較單純,只需爬取職位描述:
演示代碼如下:
detail_url = "http://www.nlpjob.com/job/61376/"
resp = requests.get(detail_url)
text = resp.content.decode("utf-8")
soup = BeautifulSoup(text, "html5lib")
job_description = soup.find('div', id='job-description')
job_description.get_text().replace('\t', '')
完整代碼
以下爲 demo 的完整代碼,其中對html源碼的解析的大邏輯前文已經介紹,部分細節不再詳述。
本文只爬取了2019年以後的職位信息(1-28頁),,
每一頁耗時約1分鐘,,
如下:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import pandas as pd
import re
import requests
from bs4 import BeautifulSoup
import time
def mul_delimiters_split(text, *dels):
'''多個分隔符對字符串進行分割,並且分隔符可以是長短不一的'''
delimiters = dels
regexPattern = '|'.join(map(re.escape, delimiters)) # 'a|\\.\\.\\.|\\(C\\)'
return re.split(regexPattern, text)
def parse_url_list(url):
'''
爬取並解析職位列表頁面數據
'''
resp = requests.get(url)
text = resp.content.decode("utf-8")
soup = BeautifulSoup(text, "html5lib")
data = []
content = soup.find_all('div', class_="row-alt")
content += soup.find_all('div', attrs={'class': "row"})
for cont in content:
# 發佈時間
time_posted = cont.find_all('span', class_="time-posted")[0].get_text()
time_posted = time_posted.replace(' ', '', )
row_info = cont.find_all('span', class_="row-info")[0]
# 職位類型
job_type = row_info.find_all('img')[0]['alt']
# 詳情鏈接
href = row_info.find_all('a')[0]['href']
# 內容標題/摘要
title = row_info.find_all('a')[0].get_text() #.get_text() = .strings
# 公司&工作地點
la = mul_delimiters_split(row_info.get_text(), ' at ', ' in ', ', ')[1:]
# 儲存結果
result = {'time_posted': time_posted,
'job_type': job_type,
'href': href,
'job_description': parse_url_detail(href),
'title': title,
'company': la[0],
'location': la[1].replace('\t', '')
}
data.append(result)
return pd.DataFrame(data)
def parse_url_detail(detail_url):
'''
爬取並解析職位詳情頁數據
'''
resp = requests.get(detail_url)
text = resp.content.decode("utf-8")
soup = BeautifulSoup(text, "html5lib")
job_description = soup.find('div', id='job-description')
return job_description.get_text().replace('\t', '')
def main():
urls = ["http://www.nlpjob.com/jobs/?p=" + str(i) for i in range(1, 28)]
data = pd.DataFrame()
for url in urls:
s = time.time()
print('正在爬取' + url + ' ...')
temp = parse_url_list(url)
data = data.append(temp)
print('完成!耗時 %s' %(time.time() - s))
# 保存
data.to_csv('../data/jobs.csv')
return data
if __name__ == '__main__':
data = main()
最後的結果示例:
-------完--------