Python爬虫实例-获取页面内大量url里的数据
一、前言
本章以某政府公开招标中标网站为实例,进行某一个指定类别的中标数据挖掘,并将这些数据存储到excel表格中。
网站:http://jsggzy.jszwfw.gov.cn/jyxx/tradeInfonew.html
需要挖掘内容:近两三个月的交通工程的中标结果公告(里的详细内容)
在使用python爬虫过程中有以下几点要注意!!
(1)遵守网站的 robots.txt
大多数网站都会有一个 robots.txt 文件,指定爬虫允许访问的路径。确保爬虫遵守这些规则,以避免不必要的法律问题和道德冲突。
(2)控制请求频率
不要频繁地向服务器发送请求,以免给服务器带来过大的压力,导致 IP 被封禁。可以使用 time.sleep() 方法在请求之间添加延迟。
昨天我抓了一天,下午的时候请求中断了一次。过了一会再试就好了~
import time
time.sleep(1) # 休眠 1 秒
(3)处理异常
网络请求过程中可能会发生各种异常情况,如连接超时、目标网页不可访问等。确保代码有适当的异常处理机制。
try:
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status()
except requests.exceptions.RequestException as e:
print(f'Error: {e}')
二、Python爬虫简介
Python 爬虫是一种用于自动化网页数据抓取的工具。使用 Python 进行网页爬虫的过程一般包括以下几个步骤:
(1)发送请求:向目标网页发送 HTTP 请求,并获取响应内容。
(2)解析内容:将获取到的网页内容进行解析,提取出需要的数据。
(3)保存数据:将提取到的数据进行存储,通常保存在文件、数据库等。
最常使用的库有 requests 和 BeautifulSoup 库,需要安装。可以使用以下命令进行安装:
pip install requests
pip install beautifulsoup4
对于更复杂的需求,可以考虑使用以下工具:
Scrapy:一个功能强大的爬虫框架,适用于大规模爬取。
Selenium:适用于需要处理动态内容或模拟用户操作的场景。
lxml:高性能的 HTML/XML 解析库。
三、代码解析
导入相应的库
import time
import requests
import pandas as pd
from datetime import datetime, timedelta
import threading
from bs4 import BeautifulSoup
import re
import json
from dateutil.relativedelta import relativedelta
点开页面,发现里面有大量的标题,每个中标公告标题都对应一个url。先获取页面里的所有url。
#提取页面里的URL地址
def get_content_list(start_page,url_list):
now = datetime.now()
end_of_day =now.replace(hour=23, minute=59, second=59, microsecond=0)
# Calculate the date three months ago
three_months_ago = now - relativedelta(months=3)
# Set the time to 00:00:00 for three months ago
start_of_date = three_months_ago.replace(hour=0, minute=0, second=0, microsecond=0)
# Convert datetime objects to strings
end_of_day_str = end_of_day.strftime("%Y-%m-%d %H:%M:%S")
start_of_date_str = start_of_date.strftime("%Y-%m-%d %H:%M:%S")
json_payload_template = '''{
"token": "",
"pn": %d,
"rn": 20,
"sdt": "",
"edt": "",
"wd": "",
"inc_wd": "",
"exc_wd": "",
"fields": "title",
"cnum": "001",
"sort": "{\\"infodatepx\\":\\"0\\"}",
"ssort": "title",
"cl": 200,
"terminal": "",
"condition": [
{
"fieldName": "categorynum",
"isLike": true,
"likeType": 2,
"equal": "003002"
}
],
"time": [
{
"fieldName": "infodatepx",
"startTime": "%s",
"endTime": "%s"
}
],
"highlights": "title",
"statistics": null,
"unionCondition": null,
"accuracy": "",
"noParticiple": "1",
"searchRange": null,
"isBusiness": "1"
}'''
url ='http://jsggzy.jszwfw.gov.cn/inteligentsearch/rest/esinteligentsearch/getFullTextDataNew'
headers ={
'Content-Length':'496',
'Content-Type':'application/json;charset=UTF-8',
'Cookie':'_gscu_2113302982=183544626ew3vg62; _gscbrs_2113302982=1; _gscs_2113302982=t18697349res1xs17|pv:1',
'Host': 'jsggzy.jszwfw.gov.cn',
'Origin':'http://jsggzy.jszwfw.gov.cn',
'Referer':'http://jsggzy.jszwfw.gov.cn/jyxx/tradeInfonew.html?type=jsgc',
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',
'X-Requested-With':'XMLHttpRequest'
}
session = requests.Session()
# Loop to create and send payloads with different "pn" values
pn_value = start_page * 20
# Insert the formatted date strings into the JSON payload
json_payload = json_payload_template % (pn_value,start_of_date_str, end_of_day_str)
# Convert JSON string to Python dictionary
payload = json.loads(json_payload)
res = session.post(url,headers=headers,json=payload)
res_json = res.json()
content = res_json
for i in range(20):
if content['result']['records'][i]['categorynum'] == "003002004": #接口,每个类别不一样,打开一个页面自己点击f12查看
url_content = content['result']['records'][i]['linkurl']
publish_date = content['result']['records'][i]['infodateformat']
city = content['result']['records'][i]['fieldvalue']
full_url = 'http://jsggzy.jszwfw.gov.cn' + url_content
# print(full_url)
url_list.append({
#url_list含有:url地址,发表日期(也就是中标日期)还有城市
"url": full_url,
"publish_date": publish_date,
"city": city
})
return content,url_list
去除重复的ur地址:
def remove_duplicates_by_url(url_list):
seen_urls = set()
unique_list = []
for entry in url_list:
url = entry['url']
if url not in seen_urls:
seen_urls.add(url)
unique_list.append(entry)
return unique_list
使用正则表达式提取每个字段。这里写得有点复杂,是因为之前生成的有的字段内容会连着下一个字段标题,查了资料还是把它直接写死了,让各个字段之间划分明确。
import requests
from bs4 import BeautifulSoup
import re
# 定义提取关键字段信息的函数
def extract_keyword_info(text):
patterns = {
'项目名称': r'项目名称\s*[::]\s*(.*?)\s*(?=(项目编号|标段名称|标段编号|中标单位|中标金额|项目负责人|招标人|招标代理|$))',
'项目编号': r'项目编号\s*[::]\s*(.*?)\s*(?=(项目名称|标段名称|标段编号|中标单位|中标金额|项目负责人|招标人|招标代理|$))',
'标段名称': r'标段名称\s*[::]\s*(.*?)\s*(?=(项目编号|项目名称|标段编号|中标单位|中标金额|项目负责人|招标人|招标代理|$))',
'标段编号': r'标段编号\s*[::]\s*(.*?)\s*(?=(项目编号|标段名称|项目名称|中标单位|中标金额|项目负责人|招标人|招标代理|$))',
'中标单位': r'中标单位\s*[::]\s*(.*?)\s*(?=(项目编号|标段名称|标段编号|项目名称|中标金额|项目负责人|招标人|招标代理|$))',
'中标金额': r'中标金额(元/费率)\s*[::]\s*(.*?)\s*(?=(项目编号|标段名称|标段编号|中标单位|项目名称|项目负责人|招标人|招标代理|$))',
'项目负责人': r'项目负责人\s*[::]\s*(.*?)\s*(?=(项目编号|标段名称|标段编号|中标单位|中标金额|项目名称|招标人|招标代理|$))',
'招标人': r'招标人\s*[::]\s*(.*?)\s*(?=(项目编号|标段名称|标段编号|中标单位|中标金额|项目负责人|项目名称|招标代理|$))',
'招标代理': r'招标代理\s*[::]\s*(.*?)\s*(?=(项目编号|标段名称|标段编号|中标单位|中标金额|项目负责人|招标人|项目名称|$))',
}
# 初始化结果字典
result = {
'项目编号': '',
'标段名称': '',
'标段编号': '',
'中标单位': '',
'中标金额': '',
'项目负责人': '',
'招标人': '',
'招标代理': '',
}
# 使用正则表达式提取字段内容
for key, pattern in patterns.items():
match = re.search(pattern, text)
if match:
result[key] = match.group(1).strip()
return result
handle_html_whole函数用于处理排序后的 HTML 列表,并提取联系信息数据。注意在函数中添加请求检查,并且对于HTML页面的空格、分行等要特别注意,具体情况具体分析。
# 定义处理 HTML 的主函数
def handle_html_whole(url_list):
contact_info_list = []
session = requests.Session()
for url_info in url_list:
url = url_info['url']
try:
res = session.get(url)
res.raise_for_status() # 检查请求是否成功
content_type = res.headers.get('Content-Type')
# 处理 HTML 响应
if 'text/html' in content_type:
soup = BeautifulSoup(res.text, 'html.parser')
# 提取标题
title = soup.find('h2', class_='ewb-trade-h').text.strip() if soup.find('h2', 'ewb-trade-h') else 'No Title'
title = title.replace('中标结果公告', '').replace('-', '').strip() # 移除“中标结果公告”六个字并去掉多余的空格和可能的前导字符
# 提取发布时间信息
pub_info = soup.find('div', class_='ewb-trade-info').text.strip() if soup.find('div', 'ewb-trade-info') else 'No Content'
# 提取公告正文
main_content_store = soup.find('div', class_='ewb-trade-con').text.strip() if soup.find('div', 'ewb-trade-con') else 'No Content'
main_content_div = soup.find('div', 'ewb-trade-con')
main_content = ''
if main_content_div:
# 从公告正文 <div> 中提取所有子节点的文本,确保可读性
main_content_parts = []
for child in main_content_div.descendants:
if isinstance(child, str):
# 直接字符串内容
main_content_parts.append(child.strip())
elif child.name in ['p', 'br', 'div']:
# 在某些标签之间添加空格或换行符以确保可读性
main_content_parts.append('\n')
main_content = ' '.join(main_content_parts).strip()
# 清理掉不间断空格字符并将多个换行符减少到一个
main_content = main_content.replace('\xa0', ' ')
main_content = re.sub(r'\n+', '\n', main_content)
# 使用正则表达式提取信息
extracted_info = extract_keyword_info(main_content_store)
# 将提取的信息添加到结果列表中
contact_info_list.append({
"网址": url,
"发布日期": url_info['publish_date'],
"所属城市": url_info['city'],
"项目名称": title,
"项目编号": extracted_info['项目编号'],
"标段名称": extracted_info['标段名称'],
"标段编号": extracted_info['标段编号'],
"中标单位": extracted_info['中标单位'],
"中标金额": extracted_info['中标金额'],
"项目负责人": extracted_info['项目负责人'],
"招标人": extracted_info['招标人'],
"招标代理": extracted_info['招标代理'],
"公告全部正文": main_content_store,
})
else:
print(f"未处理的内容类型: {content_type}")
except requests.exceptions.RequestException as e:
print(f"请求失败 {url}: {e}")
return contact_info_list
接着,写获取url和信息的主函数,本来range后我写的300,但是报错了,应该是抓取不到那么多数据。
# 获取URL和信息的主函数
url_list = []
for i in range(120):
content, url_list = get_content_list(i, url_list)
print(len(url_list))
unique_list = remove_duplicates_by_url(url_list)
sorted_list = sorted(unique_list, key=lambda x: x['publish_date'], reverse=True)
print(len(unique_list))
显示741,表示抓取了741条url记录。
contact_info_data = handle_html_whole(sorted_list)
# 从contact_info_data提取信息
result_table = []
for info in contact_info_data:
text = info['公告全部正文']
text = re.sub(r'([::])[\s\n]+', r'\1', text)
result = extract_keyword_info(text)
combined_dict = {**info, **result}
result_table.append(combined_dict)
查看Dataframe
df = pd.DataFrame(result_table)
print(df)
把结果导入到excel表格里就可以啦
excel_file_path = "C:/Users/aa/Desktop/bid_list.xlsx"
df.to_excel(excel_file_path, index=False)