Python爬虫实例附代码-获取页面内大量url里的数据

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)
  • 20
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值