普通视图

发现新文章,点击刷新页面。
今天 — 2025年9月3日首页

用 MainActor.assumeIsolated 解决旧 API 与 Swift 6 适配问题

作者 Fatbobman
2025年9月3日 22:00

尽管 Swift 提供严格并发检查已有一段时间,但许多苹果官方 API 仍未对此进行充分适配,这种情况可能还会持续相当长的时间。随着 Swift 6 的逐步普及,这个问题变得愈发突出:开发者一方面希望享受 Swift 编译器带来的并发安全保障,另一方面又对如何让代码满足编译要求感到困惑。本文将通过一个 NSTextAttachmentViewProvider 的实现案例,介绍 MainActor.assumeIsolated 在特定场景下的妙用。

莎普爱思:股东陈德康拟减持公司不超2%股份

2025年9月3日 16:24
36氪获悉,莎普爱思发布公告,持股11.67%的大股东陈德康计划自公告披露之日起15个交易日后的3个月内,通过集中竞价交易和大宗交易方式,减持公司股份合计不超过751.85万股,即不超过公司总股本的2%。

中煤在云南镇康成立新能源公司,注册资本1800万

2025年9月3日 16:22
36氪获悉,爱企查App显示,近日,中煤镇康新能源有限公司成立,法定代表人为朱传鹏,注册资本1800万元人民币,经营范围为发电业务、输电业务、供(配)电业务,建设工程监理。股东信息显示,该公司由中煤绿能科技(北京)有限公司全资持股。

印度8月服务业PMI增长创15年新高,但通胀压力加剧

2025年9月3日 16:17
周三公布的一项商业调查显示,受强劲需求推动,印度8月服务业增长提速至15年高点,与此同时,物价涨幅也达到了逾10年来的最快水平。此前,官方数据显示,这个亚洲第三大经济体上季度增速达到7.8%,远高于预期。但特朗普政府对美国进口的印度商品加征50%高额关税,这可能会抑制印度未来几个季度的经济增长。(新浪财经)

恒指收跌0.6%,医药股走强

2025年9月3日 16:11
36氪获悉,恒指收跌0.6%,恒生科技指数跌0.78%;医药生物、零售、有色金属板块领涨,药明合联涨超9%,招金矿业、阿里健康涨超4%;家电、国防军工、硬件设备板块跌幅居前,华显光电跌超7%,光启科学跌超6%,海信家电跌超5%;南向资金净买入55.08亿港元。

H5的Form表单项不够灵活怎么办?来看看这篇通用组件封装思路分享

作者 Jolyne_
2025年9月3日 16:09

前言

公司用的 react + antd 的技术栈。因为antd自带的组件并不是很灵活。所以团队封装了了类似FormItem表单项的组件,方便使用。

比如,公司有下面的交互

image.png

这个页面,有一个渠道表单项,点击它,弹出下拉列表

企业微信截图_17568855445849.png

并支持搜索

image.png

选择以及后续打开回填

image.png

image.png

渠道筛选项TXChannelPickerItem

首先该组件的使用直接套用在 Form.Item 里面即可,值直接由Form表单记录,使用的时候可以抛去心智负担

    <Form.Item
      name="channelId"
      className="required_form_item"
      label="渠道"
      rules={[{ required: true, message: "请选择渠道" }]}
      clickable
    >
      <TXChannelPickerItem />
    </Form.Item>
  );

TXChannelPickerItem 里面分为两部分,一部分就是表单项的形式,一部分就是底部弹出层

export interface ITXChannelPickerItemProps {
  value?: ITreeItem[];
  onChange?: (v?: ITreeItem[]) => void;
  placeholder?: string;
  disabled?: boolean;
  className?: string;
  transformData?: (value: TRecord[]) => IOption[];
  /** @param 额外参数 */
  extraReq?: Partial<IReqBusinessV1ChannelGetPage>;
  /**@param 初始化时,是否需要回填任意一条数据 */
  needBackFill?: boolean;
  }

export const TXChannelPickerItem = function TXChannelPickerItem_(props: ITXChannelPickerItemProps) {
  return (
    <>
      {/* 表单项内容  */}
      <div></div>
      {/* 底部弹出层内容 */}
      <ChannelPicker/>
    </>
  );
};

先看表单项内容

<>
  {/* 上 */}
  <div
    className=" absolute top-0 left-0 w-full h-full opacity-0"
    onClick={() => {
      if (disabled) {
        return;
      }
      channelRef.current?.openModal({
        initValue: value,
      });
    }}
  ></div>
  {/* 下 */}
  <div className={className}>
    <div className="w-max max-w-full overflow-hidden flex">
      <span
        className={classNames({
          "text-[#ccccd6]": true,
          hidden: !!text,
        })}
      >
        {placeholder}
      </span>
      <div
        className={classNames({
          "flex-1 wes": true,
          hidden: !text,
        })}
      >
        {text}
      </div>
    </div>
  </div>
</>;

上部分是用绝对定位盖在了筛选项上,同时满足一些情况下禁用的业务逻辑

下部分就是placeHolder和选择的值

然后再来看 弹出层内容

const channelRef = useRef<IChannelPickerRef>(null);
<ChannelPicker
  {...rest}
  ref={channelRef}
  onSelect={(_, full) => {
    onChange?.(full);
  }}
  extraReq={{
    enableFlag: true,
    ...extraReq,
  }}
/>;

弹出层用的是antd的PickerView或者CascaderView实现的,弹出层组件内部会维护状态和相关业务逻辑。然后直接通过props将状态向父组件暴露

继续看ChannelPicker内部是怎么划分的

// 选广告渠道
import { observer, useSyncProps, useWhen } from "@quarkunlimit/qu-mobx";
import { Popup } from "antd-mobile";
import { forwardRef, useImperativeHandle } from "react";
import { IChannelPickerProps, IChannelPickerRef } from "./interface";
import { PickerContent } from "./modules/PickerContent";
import { SearchRow } from "./modules/SearchRow";
import { TopOption } from "./modules/TopOption";
import { Provider, useStore } from "./store/RootStore";

const ChannelPicker = observer(
  forwardRef<IChannelPickerRef, IChannelPickerProps>(
    function ChannelPicker_(props, ref) {
      const root = useStore();
      useSyncProps(root, Object.keys(props), props);
      const { logic } = root;

      useImperativeHandle(ref, () => {
        return {
          openModal: logic.openModal,
          closeModal: logic.closeModal,
        };
      });

      useWhen(
        () => true,
        () => {
          logic.init();
        }
      );

      return (
        <Popup
          visible={logic.open}
          bodyStyle={{
            height: "70vh",
            display: "flex",
            flexDirection: "column",
          }}
          onMaskClick={logic.closeModal}
        >
          <TopOption />
          <SearchRow />
          <PickerContent />
        </Popup>
      );
    }
  )
);

export default observer(
  forwardRef<IChannelPickerRef, IChannelPickerProps>(
    function ChannelPickerPage(props, ref) {
      return (
        <Provider>
          <ChannelPicker {...props} ref={ref} />
        </Provider>
      );
    }
  )
);
  • 初始化时 init 调接口
  • DOM结构分为顶部操作按钮、搜索框、数据源

其中 PickerContent 是通过CascaderView实现, 当然你也可以使用 PickerView 实现。PickerView还可以自定义渲染每一行的内容。而CascaderView只能通过 options 配置项形式渲染

import { observer } from "@quarkunlimit/qu-mobx";
import { useStore } from "../store/RootStore";
import { CascaderView } from "antd-mobile";

export const PickerContent = observer(function PickerContent_() {
  const root = useStore();
  const { logic, computed } = root;
  return (
    <CascaderView
      style={{
        "--height": "calc(70vh - 130px)",
      }}
      className="flex-1"
      value={logic.value}
      options={logic.dataSource}
      loading={computed.loading}
      onChange={logic.onChangeValue}
    />
  );
});

至此,一个类似于Select交互操作的H5表单项就封装完成了。需要使用时,直接套用在Form.Item里面即可。

公司其余的组件也是这样的方式实现的

整体代码

import ChannelPicker from "@/pages/Picker/ChannelPicker";
import { IChannelPickerRef } from "@/pages/Picker/ChannelPicker/interface";
import { IReqBusinessV1ChannelGetPage } from "@/service/business/v1/channel/get-page";
import { IOption, ITreeItem, TRecord } from "@/utils/interface";
import { classNames } from "@/utils/tools";
import { useRef } from "react";

export interface ITXChannelPickerItemProps {
  value?: ITreeItem[];
  onChange?: (v?: ITreeItem[]) => void;
  placeholder?: string;
  disabled?: boolean;
  className?: string;
  transformData?: (value: TRecord[]) => IOption[];
  /** @param 额外参数 */
  extraReq?: Partial<IReqBusinessV1ChannelGetPage>;
  /**@param 是否需要回填任意一条数据 */
  needBackFill?: boolean;
}

export const TXChannelPickerItem = function TXChannelPickerItem_(
  props: ITXChannelPickerItemProps
) {
  const {
    value = [],
    onChange,
    placeholder = "请选择渠道",
    disabled,
    className,
    extraReq,
    ...rest
  } = props;
  const channelRef = useRef<IChannelPickerRef>(null);

  let text = "";
  if (value.length > 0) {
    text = value[value.length - 1].label;
  }

  return (
    <>
      <div
        className=" absolute top-0 left-0 w-full h-full opacity-0"
        onClick={() => {
          if (disabled) {
            return;
          }
          channelRef.current?.openModal({
            initValue: value,
          });
        }}
      ></div>
      <div className={className}>
        <div className="w-max max-w-full overflow-hidden flex">
          <span
            className={classNames({
              "text-[#ccccd6]": true,
              hidden: !!text,
            })}
          >
            {placeholder}
          </span>
          <div
            className={classNames({
              "flex-1 wes": true,
              hidden: !text,
            })}
          >
            {text}
          </div>
        </div>
      </div>
      <ChannelPicker
        {...rest}
        ref={channelRef}
        onSelect={(_, full) => {
          onChange?.(full);
        }}
        extraReq={{
          enableFlag: true,
          ...extraReq,
        }}
      />
    </>
  );
};

// 选广告渠道
import { observer, useSyncProps, useWhen } from "@quarkunlimit/qu-mobx";
import { Popup } from "antd-mobile";
import { forwardRef, useImperativeHandle } from "react";
import { IChannelPickerProps, IChannelPickerRef } from "./interface";
import { PickerContent } from "./modules/PickerContent";
import { SearchRow } from "./modules/SearchRow";
import { TopOption } from "./modules/TopOption";
import { Provider, useStore } from "./store/RootStore";

const ChannelPicker = observer(
  forwardRef<IChannelPickerRef, IChannelPickerProps>(
    function ChannelPicker_(props, ref) {
      const root = useStore();
      useSyncProps(root, Object.keys(props), props);
      const { logic } = root;

      useImperativeHandle(ref, () => {
        return {
          openModal: logic.openModal,
          closeModal: logic.closeModal,
        };
      });

      useWhen(
        () => true,
        () => {
          logic.init();
        }
      );

      return (
        <Popup
          visible={logic.open}
          bodyStyle={{
            height: "70vh",
            display: "flex",
            flexDirection: "column",
          }}
          onMaskClick={logic.closeModal}
        >
          <TopOption />
          <SearchRow />
          <PickerContent />
        </Popup>
      );
    }
  )
);

export default observer(
  forwardRef<IChannelPickerRef, IChannelPickerProps>(
    function ChannelPickerPage(props, ref) {
      return (
        <Provider>
          <ChannelPicker {...props} ref={ref} />
        </Provider>
      );
    }
  )
);

永安药业:实控人、董事长陈勇解除留置

2025年9月3日 16:04
36氪获悉,永安药业发布公告,公司5月6日披露,公司实控人、董事长陈勇被鹤峰县监察委员会立案调查并实施留置。近日,公司收到陈勇家属的通知,鹤峰县监察委员会已解除对陈勇的留置措施。目前,陈勇已能正常履行公司董事长的职责,公司生产经营情况正常。

在 Nest.js 中实现文件上传

2025年9月3日 15:52

文件上传是后端开发中非常常见的需求,例如用户上传头像、Excel 报表、图片资源等。Nest.js 作为一个基于 TypeScript 的渐进式 Node.js 框架,提供了开箱即用的上传支持,并且与 Multer 中间件深度集成。

本文将介绍 Nest.js 中文件上传的实现方式。


1. 文件上传的原理

Nest.js 本身并不直接处理文件,而是通过 Multer 来解析 multipart/form-data 类型的请求。Nest.js 提供了内置的拦截器(Interceptor),可以很方便地将上传逻辑融入到控制器中。

核心要点:

  • 使用 @UseInterceptors() 配合 FileInterceptorFilesInterceptor
  • 上传后的文件会自动保存到磁盘(或内存),并在控制器方法中以参数形式注入。
  • 可以自定义存储路径、文件名、过滤器等。

2. 单文件上传

首先安装依赖:

npm install @nestjs/platform-express multer

控制器代码示例:

import {
  Controller,
  Post,
  UploadedFile,
  UseInterceptors,
} from "@nestjs/common";
import { FileInterceptor } from "@nestjs/platform-express";
import { diskStorage } from "multer";
import { extname } from "path";

@Controller("upload")
export class UploadController {
  @Post("single")
  @UseInterceptors(
    FileInterceptor("file", {
      storage: diskStorage({
        destination: "./uploads", // 文件保存路径
        filename: (req, file, callback) => {
          const uniqueSuffix =
            Date.now() + "-" + Math.round(Math.random() * 1e9);
          callback(null, `${uniqueSuffix}${extname(file.originalname)}`);
        },
      }),
      limits: { fileSize: 5 * 1024 * 1024 }, // 限制 5MB
    })
  )
  uploadSingle(@UploadedFile() file: Express.Multer.File) {
    return {
      filename: file.filename,
      path: file.path,
    };
  }
}

说明:

  • FileInterceptor('file') 对应前端表单里的 name="file"
  • diskStorage 用于定义保存目录和文件命名规则。

JavaScript 趣味编程:从基础循环到函数式,解锁打印三角形的 N 种姿势

2025年9月3日 15:46

JavaScript 趣味编程:从基础循环到函数式,解锁打印三角形的 N 种姿势

对于许多程序员来说,用代码在控制台打印出一个小小的三角形,是学习生涯中一次难忘的“开窍”时刻。它不仅仅是一个简单的练习,更是我们理解循环、逻辑和模式识别的起点。

今天,我们不只满足于实现它,而是要玩出花样。本文将带你从最经典的双重 for 循环,一路走到更现代、更优雅的函数式写法,让你彻底掌握打印三角形的各种“姿势”。

第一站:梦开始的地方 —— 直角三角形

我们的第一个目标是打印出这样的形状(以5行为例):

*
**
***
****
*****
写法一:双重 for 循环——经典永不过时

这是每个初学者都会接触到的方法,它完美地诠释了嵌套循环的魅力。

function printRightTriangleFor(rows) {
  // 外层循环控制行数 (i)
  for (let i = 1; i <= rows; i++) {
    let rowString = '';
    // 内层循环控制每一行打印的星号数量 (j)
    // 第 i 行,就有 i 个星号
    for (let j = 1; j <= i; j++) {
      rowString += '*';
    }
    console.log(rowString);
  }
}

printRightTriangleFor(5);

思路解析: 外层循环 i 像一个“行长”,负责决定当前是第几行。内层循环 j 是一个“工人”,i 告诉它这一行需要多少个 *,它就勤勤恳恳地拼接多少个。简单、直观,逻辑清晰。

写法二:巧用 .repeat() —— 告别内层循环

进入现代 JavaScript 的世界,我们可以用更简洁的方式完成同样的工作。ES6 的 String.prototype.repeat() 方法简直是为此而生。

function printRightTriangleRepeat(rows) {
  for (let i = 1; i <= rows; i++) {
    // 在第 i 行,直接生成一个重复 i 次的 '*' 字符串
    console.log('*'.repeat(i));
  }
}

printRightTriangleRepeat(5);

思路解析: 这种写法的代码量骤减,意图也更加明确。我们不再需要手动去拼接字符串,而是直接告诉 JavaScript:“给我一个包含 i 个星号的字符串”。代码更具可读性,也更“酷”。

写法三:函数式编程——换个“次元”看问题

如果你想让你的代码看起来更“极客”,不妨试试函数式的写法。它不使用显式的循环,而是通过数组方法来生成结果。

function printRightTriangleFunctional(rows) {
  const triangle = Array.from(
    { length: rows },           // 1. 创建一个指定长度的“空”数组
    (_, index) => '*'.repeat(index + 1) // 2. 映射每一项为所需的字符串
  ).join('\n');                 // 3. 用换行符拼接成最终成品

  console.log(triangle);
}

printRightTriangleFunctional(5);

思路解析: 这种方式将问题分解为三步:创建一个代表“行”的数组,将每一“行”映射成对应的星号字符串,最后将所有行合并。这是一种声明式的编程风格,你只关心“做什么”,而不是“怎么做”,代码非常优雅。


第二站:挑战升级 —— 等腰三角形(金字塔)

现在,让我们来点更有挑战性的。打印一个居中的金字塔(以 6 行为例)。

     *
    ***
   *****
  *******
 *********
***********

逻辑分析: 这次,每一行不仅有星号,还有前导空格。通过观察规律,我们发现对于第 i 行(i 从 1 开始):

  • 空格数量 = 总行数 - i
  • 星号数量 = (2 * i) - 1

有了这个公式,我们可以解锁多种实现方式。

姿势一:单循环 + .repeat() (最常用)

这是最直接、清晰的实现方式,将上面的数学公式直接翻译成代码。

function printIsoscelesTriangle(rows) {
  for (let i = 1; i <= rows; i++) {
    // 计算并生成前导空格
    const spaces = ' '.repeat(rows - i);
    // 计算并生成星号
    const stars = '*'.repeat((2 * i) - 1);
    // 拼接并打印
    console.log(spaces + stars);
  }
}

printIsoscelesTriangle(6);
姿势二:函数式 Array.from + map

延续直角三角形的函数式思路,我们同样可以一行代码解决问题。

function pyramidFunctional(n) {
  const rows = Array.from({ length: n }, (_, idx) => {
    const i = idx + 1;
    return ' '.repeat(n - i) + '*'.repeat(2 * i - 1);
  });
  console.log(rows.join('\n'));
}

pyramidFunctional(6);
姿势三:递归生成

对于喜欢挑战的同学,递归也是一个有趣的选择。

function pyramidRecursive(n, i = 1, out = []) {
  if (i > n) {
    console.log(out.join('\n'));
    return;
  }
  out.push(' '.repeat(n - i) + '*'.repeat(2 * i - 1));
  pyramidRecursive(n, i + 1, out);
}

pyramidRecursive(6);
更多姿势一览

JavaScript 的灵活性远不止于此,我们还可以使用 while 循环、reduce 归纳、甚至是 Generator(生成器)来实现同样的效果。

  • while 循环版本:

    function pyramidWhile(n) {
      let i = 1;
      while (i <= n) {
        console.log(' '.repeat(n - i) + '*'.repeat(2 * i - 1));
        i++;
      }
    }
    pyramidWhile(6);
    
  • 生成器 (Generator) 逐行输出:

    function* pyramidGen(n) {
      for (let i = 1; i <= n; i++) {
        yield ' '.repeat(n - i) + '*'.repeat(2 * i - 1);
      }
    }
    for (const line of pyramidGen(6)) {
      console.log(line);
    }
    

最终站:封装归纳 —— 打造一个通用的图形打印机

作为一名优秀的开发者,我们追求的是代码的复用性。让我们把上面的逻辑封装成一个更通用的函数,让它能打印不同类型、不同行数、甚至不同字符的图形。

/**
 * 打印各种形状的函数
 * @param {number} rows - 总行数
 * @param {string} shape - 'right', 'inverted-right', 'isosceles'
 * @param {string} char - 用于打印的字符,默认为 '*'
 */
function printShape(rows, shape = 'right', char = '*') {
  console.log(`\n--- 打印 ${rows} 行的 ${shape} 三角形 (字符: ${char}) ---`);
  
  switch (shape) {
    case 'right':
      for (let i = 1; i <= rows; i++) console.log(char.repeat(i));
      break;
    case 'inverted-right': // 新增倒立直角
      for (let i = rows; i >= 1; i--) console.log(char.repeat(i));
      break;
    case 'isosceles':
      for (let i = 1; i <= rows; i++) {
        const spaces = ' '.repeat(rows - i);
        const chars = char.repeat((2 * i) - 1);
        console.log(spaces + chars);
      }
      break;
    default:
      console.log('抱歉,我还不会打印这种形状。');
  }
}

// 让我们来试试这个强大的打印机!
printShape(7, 'right', '#');
printShape(6, 'isosceles', '+');
printShape(5, 'inverted-right'); // 打印倒立直角三角形

总结

从一个简单的打印三角形问题,我们回顾了经典的循环逻辑,学习了现代 JavaScript 的简洁语法,体验了函数式编程的优雅,并最终将其封装成一个可复用的工具函数。

这正是编程的乐趣所在:同一个问题,总有更多、更好、更有趣的解法等待我们去发现。

现在,轮到你了!你还能想到哪些有趣的写法?或者,你能挑战一下打印一个菱形或者空心三角形吗?

Python采集京东商品评论API,json数据返回

2025年9月3日 15:45

以下是使用Python采集京东商品评论API的完整代码示例,包含JSON数据解析和反爬处理:

1. 准备工作

安装必要库:

bash
pip install requests fake_useragent

2. 核心代码实现

python
import requests
import json
import time
from fake_useragent import UserAgent
import hashlib
import random
 
class JDCommentScraper:
    def __init__(self, sku_id):
        self.sku_id = sku_id  # 商品ID(如:100012014970)
        self.base_url = "https://club.jd.com/comment/productPageComments.action"
        self.ua = UserAgent()
        
    def generate_sign(self, params):
        """模拟京东签名算法(简化版)"""
        sorted_params = sorted(params.items(), key=lambda x: x[0])
        raw_str = "".join([f"{k}{v}" for k, v in sorted_params]) + "你的加密密钥"  # 实际需逆向京东签名算法
        return hashlib.md5(raw_str.encode()).hexdigest()
    
    def get_comments(self, page=1, score=0, sort_type=5):
        """获取商品评论"""
        headers = {
            "User-Agent": self.ua.random,
            "Referer": f"https://item.jd.com/{self.sku_id}.html",
        }
        
        params = {
            "productId": self.sku_id,
            "score": score,          # 0:全部, 1:差评, 2:中评, 3:好评
            "sortType": sort_type,   # 5:推荐, 6:时间
            "page": page,
            "pageSize": 10,
            "callback": "fetchJSON_comment98",  # 京东JSONP回调函数名
            "_": str(int(time.time() * 1000)),  # 时间戳
        }
        
        # 实际开发中需替换为合法签名(需逆向京东签名算法)
        # params["sign"] = self.generate_sign(params)
        
        try:
            response = requests.get(
                url=self.base_url,
                headers=headers,
                params=params,
                timeout=10
            )
            
            # 处理JSONP响应(去除回调函数名)
            if response.status_code == 200:
                json_str = response.text.replace("fetchJSON_comment98(", "").replace(");", "")
                data = json.loads(json_str)
                return data
            else:
                print(f"请求失败,状态码: {response.status_code}")
                return None
                
        except Exception as e:
            print(f"请求异常: {e}")
            return None
    
    def parse_comments(self, data):
        """解析评论数据"""
        if not data or "comments" not in data:
            return []
            
        comments = []
        for item in data["comments"]:
            comment = {
                "id": item.get("id"),
                "content": item.get("content"),
                "score": item.get("score"),
                "nickname": item.get("nickname"),
                "creation_time": item.get("creationTime"),
                "images": [img.get("imgUrl") for img in item.get("images", [])],
                "after_comment": item.get("afterUserComment", {}).get("content"),
            }
            comments.append(comment)
        return comments
    
    def run(self, max_pages=5):
        """主采集逻辑"""
        all_comments = []
        for page in range(1, max_pages + 1):
            print(f"正在采集第 {page} 页...")
            data = self.get_comments(page=page)
            if data:
                comments = self.parse_comments(data)
                all_comments.extend(comments)
                
                # 随机延迟,避免触发反爬
                time.sleep(random.uniform(1, 3))
            else:
                break
                
        return all_comments
 
# 使用示例
if __name__ == "__main__":
    scraper = JDCommentScraper(sku_id="100012014970")  # 替换为目标商品ID
    comments = scraper.run(max_pages=3)
    
    # 保存为JSON文件
    with open("jd_comments.json", "w", encoding="utf-8") as f:
        json.dump(comments, f, ensure_ascii=False, indent=2)
    
    print(f"共采集 {len(comments)} 条评论,已保存到 jd_comments.json")

3. 关键说明

  1. 签名算法

    • 京东API实际使用sign参数验证请求合法性,需逆向其JS加密逻辑(可通过浏览器调试sign生成过程)。
    • 示例中简化了签名逻辑,实际需替换为合法签名。
  2. JSONP处理

    • 京东返回的是JSONP格式(如fetchJSON_comment98({...});),需去除回调函数名后解析JSON。
  3. 反爬策略

    • User-Agent轮换:使用fake_useragent模拟不同浏览器。
    • 请求间隔:随机延迟1-3秒避免高频请求。
    • Referer头:必须携带商品页URL作为Referer。
  4. 字段解析

    • 评论内容、评分、图片、追评等关键字段已提取,可根据需求扩展。

4. 输出示例(jd_comments.json)

json
[  {    "id": 123456789,    "content": "包装完好,物流速度快,手机运行流畅",    "score": 5,    "nickname": "jd_7e8f9g",    "creation_time": "2023-01-01 10:00:00",    "images": [      "//img30.360buyimg.com/n1/s450x450_jfs/t1/213456/7/8901/123456/63d1f2a1E12345678/1a2b3c4d5e6f7g8h.jpg"    ],
    "after_comment": "使用一周后无卡顿,推荐购买!"
  },
  {
    "id": 987654321,
    "content": "屏幕有亮点,申请换货中",
    "score": 1,
    "nickname": "jd_9a8b7c",
    "creation_time": "2023-01-02 15:30:00",
    "images": [],
    "after_comment":
  }
]

大连英特尔半导体存储技术公司更名爱思开海力士

2025年9月3日 15:34
36氪获悉,爱企查App显示,近日,英特尔半导体存储技术(大连)有限公司发生工商变更,企业名称变更为爱思开海力士半导体存储技术(大连)有限公司。该公司成立于2021年7月,法定代表人为KIM YOUNG-SIK,注册资本约3.6亿元人民币,经营范围包括集成电路制造、集成电路芯片及产品制造、电子元器件制造等,由爱思开海力士半导体(大连)有限公司全资持股。变更记录显示,今年3月,该公司原股东Intel Asia Holding Limited退出。

丰田将在捷克工厂生产其欧洲首款全电动车型

2025年9月3日 15:31
丰田汽车公司周三表示,将在捷克的工厂生产一款电动车型,这将是该公司在欧洲生产的第一款全电动车型,并将在该厂投资建设一个新的电池组装设施。这家日本汽车制造商没有透露该车型的细节或生产时间,但表示将投资约6.8亿欧元(7.96亿美元)扩建其在Kolin的工厂。丰田表示,捷克政府将向该专用电池设施投资至多6400万欧元。(新浪财经)

联想SSG:AI落地进入加速期,场景与ROI成为核心焦点

2025年9月3日 15:27

“AI行业正进入一个新的阶段:从参数竞赛走向价值回归。” 在近日举行的联想方案服务业务集团(SSG)媒体交流会上,联想集团高级副总裁、首席信息官、SSG首席技术与交付官胡贯中与联想集团副总裁、业务应用服务交付负责人陈敏仪,围绕大模型迭代放缓、幻觉率挑战、ROI导向以及本地化差异等议题,分享了联想的最新洞察与实践。

应用价值抬升,ROI成为核心指标

今年是联想SSG成立的第五年,这五年也见证了生成式AI从“发烧期”进入理性落地阶段。胡贯中提到,AI仍处于一个爆发发展阶段,几乎每隔三五天就有新进展,但ROI已经逐渐成为企业衡量投入产出的关键标准。

胡贯中强调,没有坚实的数字化底座,贸然推动AI项目往往难以奏效。“真正有经验和能力的企业,要能识别合适的工具,找到正确的场景,并将技术用在刀刃上,才能真正创造价值。”

在他看来,与竞争激烈加速的ToC端不同,在ToB端相比一味追逐更大的参数规模,企业更需要关注如何借助AI创造业务成效,简而言之就是“性价比”。大模型的迭代放缓并不意味着企业应用AI的步伐受限。 

“在ToB端,客户看重的不是模型本身,而是它能否在具体场景中释放价值。”他说,“大模型往往伴随高延迟和高成本,而中小模型在许多业务流程中已足够使用。真正的竞争焦点,不在于技术极限,而在于谁能找到适配的场景并实现价值兑现。”

以智能体为例,胡贯中指出,无论是单点智能体、超级智能体,还是多智能体协同,整体的发展方向都指向更强的智能化、更高的自主性与更广的覆盖范围。“但最终能否落地,还要回到‘场景’本身,判断是否真正适合用智能体来解决问题。”

这一逻辑在联想“乐享”超级智能体上得到了印证。该系统并非依赖最新最强的大模型,而是通过意图识别、任务编排和跨系统执行,在零售和电商场景中显著提升了客户体验和运营效率。

数据显示,“乐享”已实现订单转化率提升30%、GMV增长15%、流程人效提升30%。这表明,“模型够用+场景匹配”才是释放AI价值的关键。

幻觉率无法避免,系统性方案是解法

在ROI之外,幻觉率依旧是大模型应用中的另一大核心挑战。胡贯中指出:“幻觉是这一代架构的共性,不可能在短期内完全消除。关键在于如何在不同场景下设计解法:高风险流程必须由人类校验,而低风险环节则可以逐步交给智能体完成。”

陈敏仪则补充,解决幻觉率问题不能依赖单点突破,而需要系统工程的全链路优化。“从模型工程化、多模态交互,到RAG检索与多模型协作,每一环都需要持续打磨。” 她强调,联想的优势在于 软硬件+服务的一体化全栈能力,能够为企业客户提供端到端的可靠性保障。

制造业和供应链等重点行业正在成为AI加速落地的主阵地。联想供应链智能体“iChain”便是一个代表案例。它并非依赖单一大模型,而是通过多智能体协同实时数据连接,在复杂供应链场景中实现风险预测、异常预警和库存优化。

实践结果显示,iChain将风险识别准确率提升至90%,风险响应周期缩短4倍,显著增强了企业供应链的韧性。这种“端到端、场景驱动”的解法,正是对业界在幻觉率与精准度担忧上的直接回应。

从技术突破到价值兑现

从大模型迭代放缓,到幻觉率挑战,再到ROI与本地化差异,SSG在沟通会上反复强调的逻辑是:AI的突破不只是技术层面的革新,更是一项系统工程。真正推动企业走向智能化,需要的不仅是算法能力,更需要从基础设施、平台工具到行业场景的全栈布局与实践经验。

在中外市场的对比上,海外客户更倾向通过SaaS模式直接消费AI,而国内客户则更强调“本地化+混合部署”,以确保数据安全与灵活性。联想通过“内生外化”战略,将自身在180多个国家的业务实践验证后,再对外输出方案,并结合本地化需求灵活适配。

胡贯中总结道:“无论是传统机器学习、深度学习,还是生成式AI,它们都只是工具。关键在于找到合适的场景,并以正确的方式释放价值。盲目追逐大模型,并不等于长期竞争力。”

 

AI 是不是让我们更不自由了?

作者 刘学文
2025年9月3日 15:23

最近上映的《捕风捉影》是成龙近十年来的最佳电影,虽然是个翻拍的爽片,但也加入了非常时髦的 AI 元素,比如警方开始使用超级 AI 作为抓捕行动指挥,电影开头的桥段就是警方长官没有采用 AI 建议转由自己决策,导致罪犯逃脱。

虽然后面的结局是成龙作为经验丰富的老警察,加上一定的 AI 辅助最终将犯人绳之以法,但也抛出了一个问题:人,该不该听 AI 的指挥?如果事事都听 AI 的,我们存在的意义又是什么?

GEO,与 AI 信徒

GEO(Generative Engine Optimization,生成式引擎优化)是一个非常新的概念,但并不难理解,与之对应的是 SEO(Search Engine Optimization,搜索引擎优化)。

我们在使用 Google 或者百度等搜索引擎的时候,显示出来的链接大概会受 3 个方面的影响:搜索引擎自己的算法规则,链接内容有无根据规则进行相应优化使自己排名靠前(即 SEO),用户在搜索引擎那里的用户画像。

当 DeepSeek 等 AI 工具也承担起了部分搜索任务之后,搜索结果产生的就不再是一个个需要点击的链接,而是看上去笃定的直接答案。

比如我在 Google 搜索「中国最好的 5 家科技媒体是哪些」的时候,它只会给我一堆链接,排名榜首的链接还是一个不知名机构评选的 2017 年十大科技媒体,其中排名榜首的「砍柴网」已经停更。

我问 ChatGPT 的时候,它就会直接告诉我答案和理由。

这个时候,如果哪家友媒看到这个结果发现自己没上榜,想必会有点不服气,一边暗骂 ChatGPT 不懂科技也不懂媒体,一边可能在思考,如何影响 ChatGPT,让它接受自己的优秀。

当然,这个结果意义并不大,因为几乎没多少人会给 ChatGPT 提这样的问题。不过,按照当下 ChatGPT、Perplexity 以及 DeepSeek 等 AI 工具的渗透,会有越来越多的人,去追问更多的问题:

  • 给我推荐一款物美价廉,性能出众,续航持久,坚固耐摔的手机。
  • 某某企业给我发了 offer,这家企业口碑如何,值得去吗?
  • 我今年湖北文科高考 567 分,什么大学什么专业比较好就业?

就像今年 6 月,阿里和夸克和腾讯的元宝,都针对高考志愿填报做了相应的 AI 工具以及大量的宣传。

此时,手机厂商为了卖手机,企业为了雇主形象,大学为了招生率,都有对 AI 工具生成结果进行优化的动机,GEO 就应运而生。

元力科技 GEO 事业部总经理 Claire 告诉爱范儿:

正是因为大模型通过推理和语义理解去生成结果,呈现出了非商业化的特征,所以用户普遍会认为 AI 工具给出的答案更客观,更中立可信。

 

同时,如果一些品牌和产品被 AI 答案引用,那么用户的心智渗透相对来说会更隐性以及更高效。

简言之,GEO 就是一个营销领域的富矿。

艾加营销集团元力科技首席战略官 Frank 说:

预计今年年底 ChatGPT 等 AI 搜索能占到传统搜索流量的 10% 左右,明年预估是 25%,乐观估计到 2028 年的话,AI 搜索和传统搜索流量各 50%,最保守估计也是 2030 年到这个比例。

 

现在国内 SEO 市场的规模大概是 180 亿人民币,全球是 800 亿美元的市场,如果仅从流量比例来倒推的话,长期看 GEO 市场也会是四五百亿美元的市场规模。

 

但 Frank 对 GEO 的看法并不停留在与 SEO 并列和对比的层面上:

 

SEO 实际上是在传统的数字营销里面非常细分,完全是一个工具化的东西,但行业里对 GEO 的理解已经超越了工具层面,它能够在 AI 时代里重构营销范式,尤其是重构品牌的认知度和可见度,重塑品牌和消费者的关系,从而打通完整的消费链路。

 

所以最近包括红杉,英伟达创投,A16Z 都在给 GEO 初创公司进行投资。

 

因为原来的品牌是面向人的,两点一线的沟通,现在变成了人和品牌之间,多了一个 AI 大模型代表的伙伴、助手或者秘书这样的角色。所以品牌不仅要影响人,也要去影响 AI。

相比于 SEO 规则和方法论的明确,实际上因为大模型是个黑盒子的缘故,GEO 目前还在一个探索的阶段,规则也相对模糊,Claire 说:

大模型存在不确定性,不意味着结果无法被优化。如果我们掌握了 AI 工具的内容偏好和优化规则的话,是能够显著提升品牌内容被 AI 收录和引用的概率。

 

特别是内容要从用户的意图出发,用更贴近自然语言的形式构建内容,而不能是 SEO 时代的关键词堆砌。

 

比方说我们要把握用户提问背后的真实意图,再进行品牌核心内容的语义覆盖和场景适配,持续给AI喂养真实的、高质量的内容,才能更容易让 AI 理解品牌到底好在哪里、并愿意在合适的问题场景下去引用或主动生成品牌相关的内容。对于品牌来说,AI 认知的建设是个长期工程。

 

AI 喜欢阅读的内容普遍有四个核心特点:一是要有权威性和高可信度,比如行业机构发布的调研报告和有影响力的榜单等等;第二是数据化、客观性的内容;第三是结构化、多模态内容;最后也是最重要的,就是语义相关性,确保与用户真实意图具有高匹配度。

 

同时,不同AI工具的信源抓取偏好也不同,选择高权重渠道进行投放,也能提高品牌被AI「看见」 的可能性。

相比于 SEO 依靠「关键词密度和反向链接数量」的玩法,Claire 和 Frank 都更倾向于相信 AI 大模型的选择。

用 Frank 的话来说就是,只有被 AI 大模型信赖的内容,才会被消费者信赖。这是因为大模型选择内容有一个 EAT 原则(Experience/Expertise 专业性、Authoritativeness 权威性、Trustworthiness 可信性)。

更关键的问题是,AI 的进化速度相当快,哪怕是被群嘲的 GPT-5 被认为体验不佳,但背后重要的幻觉率下降了一个量级,它看起来不够友好和高情商,但确实可信更多。

现在人们在使用搜索引擎的时候,不会仔细辨别其中的 SEO 痕迹,无论知晓这个工具与否,辨别的行为是无意义的,当 GEO 闯入 AI 工具的时候亦是如此。

与其说这是一个人类自主性的问题,不如说是一个关于信任的问题。

AI 成为了信任的中介,信任才是 GEO 背后的核心资产。

导航,与三体游戏

人类总是高估自己的自主性,常觉自己是自由的。

Waze 是导航领域的先驱,其原理是社群用户上报自己的位置和速度,包括其他的交通道路信息,甚至是交警信息等等,来给整个 Waze 的用户提供导航,规避拥堵,躲避交警等信息。

这套逻辑现在已经被诸多地图导航应用所采用,但在十多年前,基于海量用户的众包模式显得相当新锐,在智能手机早期阶段,这样一个 LBS 的服务有着巨大的商业潜力,在 2013 年,Google 收购了 Waze。

在这种模式下,万千用户贡献自己信息,以点滴之力汇聚成数据洪流,又变成导航信息。

问题来了,当 Waze 提示用户左拐回家不会拥堵,但用户自己觉得直行更快的时候,谁对的概率大?

一般是 Waze 正确,因为它掌握了更高维度的信息,在以上帝视角看整个地图。

接着,《未来简史》里提出了第二个问题:假设因为 Waze 实在太好用,所有驾车人都开始使用。再假设今天一号公路大堵车,而备选的二号公路车流相对顺畅。如果 Waze 只是让大家都知道二号公路顺畅,所有驾车人就会一窝蜂开向二号公路,最后又全堵在一起。

那么 Waze 会不会向一半的用户推送二号公路更顺畅的信息,分流一半的用户过去,但同时向另一半的用户隐瞒这个信息?

从效率最优和体验最优的角度来看,这也会发生。

在一个导航应用面前,人类所谓的自主性其实是个泡沫,人类贡献信息铸就一个数据上帝,然后又听从这个数据上帝的指挥。每个想逆其道而行之的人,都会被拥堵制裁。

▲ 电视剧《三体》还原了「人列计算机」

科幻小说《三体》里有一个更为极端的场景,在小说里面有一个虚拟的游戏叫《三体游戏》,玩家需要解出三体运动的答案,虚拟的冯·诺依曼找到了虚拟的秦始皇,秦始皇基于冯·诺依曼架构,召集 3000 万名士兵,通过每个士兵举旗的方式模拟计算机的二进制运算,组成了一个人列计算机。

这个时候,3000 万名士兵,就相当于 3000 万个晶体管,当他们听从指挥的时候,哪怕是在秦朝的背景下,也能展现出当代计算机般的运算能力。

虽然「算法」这个词并不是那么美好,但太多太多的例子告诉我们,不听算法言,吃亏在眼前。

就好像打车应用一般情况下不会用显性的惩罚去约束司机,但评分较低的司机获得优质订单的概率会更小一样,算法的大手就像佛祖的五指山。

而哪怕是对自己车技再自信的人也会承认,如果算力和传感器继续发展,人类把所有的驾驶行为都交给 AI,那么车祸率和拥堵率都会大大下降。

我们已经到了不得不承认 AI 可以是某个社会领域更好的解法的阶段,人类最好甘愿服从算法,为算法贡献数据,但不少人还在对 AI 是不是个人更好解法存在怀疑。

元力科技首席战略官 Frank 在采访里提了一个例子,现在一份《纽约时报》的信息量,相当于 17 世纪一名伦敦市民一整年的信息获取量。

而现在摆在当代人眼前的信息是无限的,只要我们愿意,我们可以一天看 16 个小时的报纸,一天摄入的信息是几百年前伦敦市民一辈子摄入的信息。

但人类的进化速度完全没法跟上信息爆炸的速度,这个时候 AI 也可以成为个人的解法。

按照这个角度看,ChatGPT 这种也只是比较初级的信息处理方式,诸如能帮助用户完成一定任务的 AI Agent 智能体最近进展不错,包括未来的通用人工智能 AGI 也被认为极有可能实现。

信息和人的关系,与信息的数量和密度息息相关,书籍时代,人们修《四库全书》,按经史子集分类,方便自主检索,这已然是人类面对海量信息催生出来的便捷信息处理方法。

搜索行为与之神似,信息进一步爆炸之后,基于用户画像的标签算法进一步帮人类过滤和筛选信息,形成高效信息匹配与所谓「信息茧房」的双刃剑,从 ChatGPT 这样的 AI 工具,到初见雏形的 AI Agent,其实人类在此之中处理信息的效率一步步提升,同时自身需要做的工作越来越少。

前面说到的 GEO 等等,不过是这种人类处理信息行为迁徙里的一些伴奏。

感官,与人类的自由意志

且不说 AI 取代人类工作这样的话题,光是看看我们在使用 AI 工具得到的结果可能被 GEO 优化过,导航明明有更畅通的道路却不提供给我,服从算法的调剂反而可以获得更好的体验实现自我更大的价值这些结论,就仿佛人文主义正在向 AI 投降,自由意志已被算法枪毙。

不过有句话说得很好:为什么说人类的容错率大得惊人?因为人类的主线任务其实就是搞到每天 2000 大卡的热量,再找一个保证热量不流失的地方,其他所有的都是支线任务。

在采访机械外骨骼创业企业 HyperShell 创始人的时候,其创始人说:

用不同的技术去增强人类本身,一定是人类文明的下一个演进阶段,这趋势我觉得是正确的,只是技术类型有很多,AI 也是一种对人的增强,只是它增强的是人的脑力,人的信息获取能力还有逻辑推导能力。

 

人类增强这件事是未来 10 年,20 年非常重要的技术方向,我们在做的其实只是其中一个细小的分支。外骨骼其实就是人和物理世界进行交互的介质,可输入,可编程,可数字化,它要实现的就是我们在物理世界的增强现实,来实现人的自由意志。

 

像钢铁侠那样的装甲和人工智能,在未来是可以想象,也可以触及的。

《未来简史》里提到了一个重要的概念,就是当代人类的感官体验和心理状态异常狭隘。

感官体验上,人类只能见到波长在 400 纳米到 700 纳米之间的电磁波,也就是人类视角的可见光,但皮皮虾拥有 16 种视锥细胞,能看到紫外线、红外线,甚至于偏振光,这个世界对于皮皮虾来说更为多彩绚烂。

但只有 3 种视锥细胞的我们还没法体验皮皮虾的视觉,就像听觉上蝙蝠可以听到人类意义上的超声波从而避障,但人类却充耳不闻一样。

不光是我们无法拥有其他动物的感官体验,远古时期人类可以通过嗅觉来感知他人情绪,古部落在争论是否要和隔壁部落开战时,只需要通过气味就能知道大家的想法,因为在鼓舞和恐惧等情绪下,人类会散发不同的气味。不过由于人类发展过程中,视觉和听觉更为重要,人类的这种嗅觉能力退化殆尽。

作者尤瓦尔·赫拉利在《未来简史》里提到了诸多类似的例子来佐证人类心智状态的愈发狭隘,并且最终指向人类可以利用技术完全控制心智状态,即「人类过往是升级版的大猩猩,未来则是放大版的蚂蚁」。

这个论断和前面说到的 Waze 导航,以及三体游戏里用 3000 万士兵组成计算机一脉相承。

的确如此,比如我们看到有人去尝试翼装飞行或者徒手攀岩这种死亡率很高的运动时,佩服和羡慕之余,心里也会评价这是「疯子行为」,另一方面,在死亡边缘徘徊所获得了心智体验,又是极为难得的。

前几年「元宇宙」概念火的时候,主张人类躲进虚拟世界沉迷虚幻享乐的「元宇宙」派和主张开拓真实物理世界人类走向星辰大海的「飞船派」一直有争论。

但当下的事实就是,与 AI 浪潮伴生的,还有「具身智能」,「脑机接口」以及「人类增强」等等,虽然物理世界的进化速度慢于代码构筑的世界,但以人类之多样性和求知探索欲,「元宇宙派」和「飞船派」当然是各有拥趸乃至融合共生的,就像如今智能汽车领域,代码和机械一样重要。

我们并非时时刻刻都坐在车上接受导航的指引,也不是科幻小说里单一场景的举旗小兵,以超然之上的上帝视角看,人类确如蝼蚁,而在平视角度看,周围是具身智能机器人,戴着内置 AI Agent 的智能眼镜,身穿提供数百马力的机械外骨骼,那我们就是可以上天遁地,时时刻刻都能翼装飞行徒手攀岩并且不惧死亡威胁的钢铁侠。

或者更疯狂一点,技术能够让我们也能体验皮皮虾的视觉,蝙蝠的听觉。

也恰好是今天,特斯拉公布了他们的《宏图计划第四篇章》。与以往聚焦汽车或能源产品的蓝图不同,特斯拉这次描绘了一个更遥远、也更理想化的终点:一个由 AI 和机器人主导的「可持续富足」的社会。是的,特斯拉的战略重心已经从「可持续发展」,转向了对未来社会形态的构想。

这起码意味着,由 AI 和机器人主导的「可持续富足」的社会实践进入到了工程阶段,而不是科幻描述阶段。

也许我们被迫了许多,但自由意志依然存在,并比以往更强。

稳中向好。

#欢迎关注爱范儿官方微信公众号:爱范儿(微信号:ifanr),更多精彩内容第一时间为您奉上。

爱范儿 | 原文链接 · 查看评论 · 新浪微博


❌
❌