找回密码
 立即注册
首页 业界区 安全 1023021226李坤铭第三次作业

1023021226李坤铭第三次作业

林鱼 2025-11-25 17:05:01
作业①:
要求:指定一个网站,爬取这个网站中的所有的所有图片,例如:中国气象网(http://www.weather.com.cn)。实现单线程和多线程的方式爬取。
1)单线程代码:

点击查看代码
  1. import os
  2. import requests
  3. from bs4 import BeautifulSoup
  4. from urllib.parse import urljoin
  5. from concurrent.futures import ThreadPoolExecutor
  6. # 基础配置
  7. TARGET_URL = "http://www.weather.com.cn"
  8. HEADERS = {
  9.     "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
  10. }
  11. def fetch_page(url):
  12.     try:
  13.         response = requests.get(url, headers=HEADERS, timeout=10)
  14.         response.raise_for_status()
  15.         return response.text
  16.     except requests.RequestException as e:
  17.         print(f"请求页面失败: {e}")
  18.         return None
  19. def extract_image_urls(html, base_url):
  20.     soup = BeautifulSoup(html, 'html.parser')
  21.     img_tags = soup.find_all('img')
  22.     img_urls = set()
  23.    
  24.     for img in img_tags:
  25.         img_url = img.get('src')
  26.         if img_url:
  27.             absolute_url = urljoin(base_url, img_url)
  28.             img_urls.add(absolute_url)
  29.    
  30.     return img_urls
  31. def download_image(img_url):
  32.     try:
  33.         print(f"开始下载: {img_url}")
  34.         response = requests.get(img_url, headers=HEADERS, timeout=10)
  35.         response.raise_for_status()
  36.         
  37.         filename = os.path.join("images", img_url.split("/")[-1])
  38.         with open(filename, 'wb') as f:
  39.             f.write(response.content)
  40.         print(f"下载成功: {filename}")
  41.     except Exception as e:
  42.         print(f"下载失败: {img_url} 错误: {str(e)}")
  43. def single_threaded_crawler():
  44.     print("=== 单线程爬虫开始 ===")
  45.     html = fetch_page(TARGET_URL)
  46.     if html:
  47.         img_urls = extract_image_urls(html, TARGET_URL)
  48.         os.makedirs("images", exist_ok=True)
  49.         
  50.         for url in img_urls:
  51.             download_image(url)
  52.     print("=== 单线程爬虫结束 ===")
  53. def multi_threaded_crawler():
  54.     print("=== 多线程爬虫开始 ===")
  55.     html = fetch_page(TARGET_URL)
  56.     if html:
  57.         img_urls = extract_image_urls(html, TARGET_URL)
  58.         os.makedirs("images", exist_ok=True)
  59.         
  60.         with ThreadPoolExecutor(max_workers=5) as executor:
  61.             executor.map(download_image, img_urls)
  62.     print("=== 多线程爬虫结束 ===")
  63. if __name__ == "__main__":
  64.     single_threaded_crawler()
  65.     multi_threaded_crawler()
复制代码
输出结果:
1.png

多线程代码:

点击查看代码
  1. import requests
  2. from bs4 import BeautifulSoup
  3. import os
  4. import time
  5. import threading
  6. from urllib.parse import urljoin, urlparse
  7. from queue import Queue
  8. class ConcurrentImageScraper:
  9.     def __init__(self, start_url, page_limit=24, image_limit=124, worker_count=5):
  10.         self.start_url = start_url
  11.         self.page_limit = page_limit
  12.         self.image_limit = image_limit
  13.         self.worker_count = worker_count
  14.         self.images_downloaded = 0
  15.         self.processed_pages = set()
  16.         self.url_queue = Queue()
  17.         self.thread_lock = threading.Lock()
  18.         self.http_session = requests.Session()
  19.         self.http_session.headers.update({
  20.             'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
  21.         })
  22.         # 初始化图片存储目录
  23.         self.storage_dir = 'downloaded_images'
  24.         os.makedirs(self.storage_dir, exist_ok=True)
  25.         # 将起始URL加入队列
  26.         self.url_queue.put(start_url)
  27.         self.processed_pages.add(start_url)
  28.     def validate_url(self, url):
  29.         """验证URL是否合法"""
  30.         parsed = urlparse(url)
  31.         return all([parsed.netloc, parsed.scheme])
  32.     def fetch_image(self, image_url, source_page):
  33.         """获取并保存图片"""
  34.         with self.thread_lock:
  35.             if self.images_downloaded >= self.image_limit:
  36.                 return False
  37.         try:
  38.             # 处理相对路径
  39.             final_url = image_url if image_url.startswith(('http://', 'https://')) \
  40.                 else urljoin(source_page, image_url)
  41.             if not self.validate_url(final_url):
  42.                 return False
  43.             # 验证图片扩展名
  44.             supported_formats = ('.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp')
  45.             if not final_url.lower().endswith(supported_formats):
  46.                 return False
  47.             print(f"{threading.current_thread().name} 正在获取图片: {final_url}")
  48.             response = self.http_session.get(final_url, timeout=10)
  49.             response.raise_for_status()
  50.             # 生成唯一文件名
  51.             filename = os.path.basename(urlparse(final_url).path) or f"img_{self.images_downloaded + 1}.jpg"
  52.             save_path = os.path.join(self.storage_dir, filename)
  53.             
  54.             # 处理文件名冲突
  55.             counter = 1
  56.             while os.path.exists(save_path):
  57.                 name, ext = os.path.splitext(filename)
  58.                 save_path = os.path.join(self.storage_dir, f"{name}_{counter}{ext}")
  59.                 counter += 1
  60.             # 写入文件
  61.             with open(save_path, 'wb') as file:
  62.                 file.write(response.content)
  63.             with self.thread_lock:
  64.                 self.images_downloaded += 1
  65.                 progress = self.images_downloaded
  66.             print(f"{threading.current_thread().name} 已保存: {filename} (进度: {progress}/{self.image_limit})")
  67.             return True
  68.         except Exception as error:
  69.             print(f"{threading.current_thread().name} 获取图片失败 {image_url}: {error}")
  70.             return False
  71.     def parse_page(self, page_url):
  72.         """解析页面内容"""
  73.         print(f"{threading.current_thread().name} 正在解析: {page_url}")
  74.         try:
  75.             response = self.http_session.get(page_url, timeout=10)
  76.             response.raise_for_status()
  77.             response.encoding = 'utf-8'
  78.             page_content = BeautifulSoup(response.text, 'html.parser')
  79.             # 提取图片链接
  80.             image_elements = page_content.find_all('img')
  81.             for img in image_elements:
  82.                 with self.thread_lock:
  83.                     if self.images_downloaded >= self.image_limit:
  84.                         return
  85.                 image_src = img.get('src') or img.get('data-src')
  86.                 if image_src:
  87.                     self.fetch_image(image_src, page_url)
  88.             # 提取后续页面链接
  89.             with self.thread_lock:
  90.                 if len(self.processed_pages) >= self.page_limit:
  91.                     return
  92.             link_elements = page_content.find_all('a', href=True)
  93.             for link in link_elements[:8]:  # 限制每页处理的链接数
  94.                 with self.thread_lock:
  95.                     if self.images_downloaded >= self.image_limit or \
  96.                        len(self.processed_pages) >= self.page_limit:
  97.                         return
  98.                 next_page = link['href']
  99.                 if not next_page.startswith('http'):
  100.                     next_page = urljoin(page_url, next_page)
  101.                 if self.start_url in next_page and \
  102.                    next_page not in self.processed_pages and \
  103.                    len(self.processed_pages) < self.page_limit:
  104.                     
  105.                     with self.thread_lock:
  106.                         if next_page not in self.processed_pages:
  107.                             self.processed_pages.add(next_page)
  108.                             self.url_queue.put(next_page)
  109.         except Exception as error:
  110.             print(f"{threading.current_thread().name} 解析页面出错 {page_url}: {error}")
  111.     def task_executor(self):
  112.         """线程任务执行器"""
  113.         while True:
  114.             with self.thread_lock:
  115.                 if self.images_downloaded >= self.image_limit or \
  116.                    (self.url_queue.empty() and len(self.processed_pages) >= self.page_limit):
  117.                     break
  118.             try:
  119.                 current_url = self.url_queue.get(timeout=5)
  120.                 self.parse_page(current_url)
  121.                 self.url_queue.task_done()
  122.             except:
  123.                 break
  124.     def run_scraper(self):
  125.         """启动爬虫"""
  126.         print("启动多线程爬虫...")
  127.         print(f"起始URL: {self.start_url}")
  128.         print(f"页面限制: {self.page_limit}")
  129.         print(f"图片限制: {self.image_limit}")
  130.         print(f"并发线程: {self.worker_count}")
  131.         print("=" * 50)
  132.         start = time.time()
  133.         # 创建工作线程
  134.         workers = []
  135.         for idx in range(self.worker_count):
  136.             worker = threading.Thread(
  137.                 target=self.task_executor,
  138.                 name=f"Worker-{idx + 1}",
  139.                 daemon=True
  140.             )
  141.             worker.start()
  142.             workers.append(worker)
  143.         # 等待所有任务完成
  144.         self.url_queue.join()
  145.         # 等待工作线程结束
  146.         for worker in workers:
  147.             worker.join(timeout=1)
  148.         duration = time.time() - start
  149.         print("=" * 50)
  150.         print("爬取任务完成!")
  151.         print(f"总耗时: {duration:.2f}秒")
  152.         print(f"已处理页面: {len(self.processed_pages)}个")
  153.         print(f"已下载图片: {self.images_downloaded}张")
  154. # 使用示例
  155. if __name__ == "__main__":
  156.     scraper = ConcurrentImageScraper(
  157.         start_url="http://www.weather.com.cn",
  158.         page_limit=24,
  159.         image_limit=124,
  160.         worker_count=5
  161.     )
  162.     scraper.run_scraper()
复制代码
输出结果:
2.png

2)心得体会:
单线程爬虫实现简单,逻辑清晰。多线程爬虫比较复杂,但效率高。
作业②
要求:熟练掌握 scrapy 中 Item、Pipeline 数据的序列化输出方法;使用scrapy框架+Xpath+MySQL数据库存储技术路线爬取股票相关信息。
1)代码:

点击查看代码
  1. import scrapy
  2. import json
  3. class EastmoneyStockSpider(scrapy.Spider):
  4.     name = 'eastmoney_stock_spider'
  5.     def initiate_requests(self):
  6.         # 东方财富A股数据接口
  7.         api_endpoints = [
  8.             'http://82.push2.eastmoney.com/api/qt/clist/get?pn=1&pz=20&po=1&np=1&ut=bd1d9ddb04089700cf9c27f6f7426281&fltt=2&invt=2&fid=f3&fs=m:0+t:6,m:0+t:13,m:0+t:80,m:1+t:2,m:1+t:23&fields=f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f12,f13,f14,f15,f16,f17,f18,f20,f21,f23,f24,f25,f22,f11,f62,f128,f136,f115,f152',
  9.         ]
  10.         
  11.         for endpoint in api_endpoints:
  12.             yield scrapy.Request(
  13.                 url=endpoint,
  14.                 callback=self.process_api_response,
  15.                 meta={'page_number': 1}
  16.             )
  17.     def process_api_response(self, response):
  18.         try:
  19.             response_data = json.loads(response.text)
  20.             stock_list = response_data.get('data', {}).get('diff', [])
  21.             
  22.             for idx, (code, details) in enumerate(stock_list.items(), start=1):
  23.                 yield {
  24.                     'rank': idx,
  25.                     'code': details.get('f12', '未知'),
  26.                     'name': details.get('f14', '未知'),
  27.                     'price': details.get('f2', '未知'),
  28.                     'change_percent': f"{details.get('f3', 0)}%",
  29.                     'change_value': details.get('f4', '未知'),
  30.                     'trading_volume': details.get('f5', '未知'),
  31.                     'trading_value': details.get('f6', '未知'),
  32.                     'price_range': f"{details.get('f7', 0)}%",
  33.                     'daily_high': details.get('f15', '未知'),
  34.                     'daily_low': details.get('f16', '未知'),
  35.                     'opening_price': details.get('f17', '未知'),
  36.                     'previous_closing': details.get('f18', '未知'),
  37.                 }
  38.         except json.JSONDecodeError:
  39.             self.logger.error(f"Failed to parse JSON response from {response.url}")
  40.         except Exception as e:
  41.             self.logger.error(f"Error processing response: {str(e)}")
  42.     def start_requests(self):
  43.         return self.initiate_requests()
复制代码
输出结果:
3.png

2)心得体会:
在本次针对东方财富网的股票数据采集实践中,我学会了如何组件Spider、Item、Pipeline、Middleware的架构,对Scrapy框架的运行机制理解更深入了。
作业③:
要求:熟练掌握 scrapy 中 Item、Pipeline 数据的序列化输出方法;使用scrapy框架+Xpath+MySQL数据库存储技术路线爬取外汇网站数据。
1)代码:

点击查看代码
  1. import scrapy
  2. from boc_forex.items import BocForexItem
  3. class BankOfChinaForexSpider(scrapy.Spider):
  4.     name = 'boc_forex_rates'
  5.     allowed_domains = ['boc.cn']
  6.     start_urls = ['https://www.boc.cn/sourcedb/whpj/']
  7.     def parse(self, response, **kwargs):
  8.         # 选择汇率数据表格中的行(跳过表头)
  9.         currency_rows = response.xpath('//table[contains(@align, "left")]/tr[position() > 1]')
  10.         
  11.         for row in currency_rows:
  12.             
  13.             yield BocForexItem(
  14.                 currency=row.xpath('./td[1]/text()').get(default='').strip(),
  15.                 tbp=row.xpath('./td[2]/text()').get(default='').strip(),      # 现汇买入价
  16.                 cash_buy_price=row.xpath('./td[3]/text()').get(default='').strip(),  # 现钞买入价
  17.                 tsp=row.xpath('./td[4]/text()').get(default='').strip(),      # 现汇卖出价
  18.                 cash_sell_price=row.xpath('./td[5]/text()').get(default='').strip(), # 现钞卖出价
  19.                 publish_time=row.xpath('./td[7]/text()').get(default='').strip()     # 发布时间
  20.             )
  21.     def handle_error(self, failure):
  22.         self.logger.error(f"Request failed: {failure.request.url}")
复制代码
输出结果:
4.png

2)心得体会:
在本次针对中国银行外汇牌价数据的采集实践中,我掌握了时间参数在URL中的传递机制。通过使用 XPath 选择器,我学会如何更高效的处理非结构化网页数据。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

2025-11-27 14:25:55

举报

您需要登录后才可以回帖 登录 | 立即注册