Spaces:
Running on Zero
Running on Zero
File size: 10,662 Bytes
422d4ca | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | """Download Amazon Reviews 2023 (Clothing_Shoes_and_Jewelry).
銆愰噸瑕佸彉鏇淬€戝畬鍏ㄦ姏寮?HuggingFace 鏁版嵁闆?API,鏀逛负鐩存帴浠?McAuley Lab 瀹樻柟
HTTPS 鏈嶅姟鍣ㄦ祦寮忔媺鍙栧師濮?jsonl.gz 鏂囦欢銆傚師鍥?
1. 鏂扮増 datasets 宸插簾寮?loading script, load_dataset(..., trust_remote_code=True)
鎶?"Dataset scripts are no longer supported"銆?2. McAuley-Lab/Amazon-Reviews-2023 浠撳簱鐨?main 鍒嗘敮鍙湁鑴氭湰鍜?raw 鐩綍,
parquet 鏂囦欢鍒嗘暎鍦?PR 鍒嗘敮(refs/pr/10)涓?涓?Clothing 绫荤洰骞朵笉瀹屾暣,
渚濊禆 HF 璧板緱闈炲父鏇叉姌涓斿鏄撹俯鍧戙€?3. 瀹樻柟鏂囨。 (amazon-reviews-2023.github.io) 缁欑殑鐩撮摼鏈€绋?
https://mcauleylab.ucsd.edu/public_datasets/data/amazon_2023/...
涓€浠?jsonl.gz,娴佸紡 GET + gunzip + readline 涓変欢濂楀嵆鍙€? 瀵?sample 妯″紡灏ゅ叾鍙嬪ソ鈥斺€斾笅澶?N 鏉″氨鎺愭柇杩炴帴,涓嶇敤涓嬪畬鏁翠釜鏂囦欢銆?4. UCSD 鏈嶅姟鍣ㄥ湪鍥藉唴璁块棶閫氬父姣?huggingface.co 绋冲畾 (HF 缁忓父 SNI 闃绘柇)銆?
銆?026-05 淇銆?- pyarrow 鍐?parquet 鏃朵細鎸夊垪鎺ㄦ柇绫诲瀷, Amazon meta 鐨?price 瀛楁鏄? "鍙兘鏁板瓧 / 鍙兘 '鈥? (em-dash) / 鍙兘 'None' / 鍙兘绌? 鐨勮剰娣峰悎瀛楁,
pyarrow 璇曞浘鎺ㄦ垚 double 鏃堕亣鍒?'鈥? 鐩存帴 ArrowInvalid銆? 瑙e喅: 鍐?parquet 鍓嶇敤 _clean_meta_for_parquet 鎶?price 寮鸿浆 float銆?- 鍚屾椂澧炲姞 jsonl 鍏滃簳鐩? meta 鎷夊畬鍏堝瓨涓€浠藉師濮?jsonl, 鍐嶅啓 parquet銆? 杩欐牱涓囦竴 parquet 鍙堝嚭鍒殑闂, 涓嬫鑳界绾ф仮澶? 涓嶅繀閲嶆柊鐖?25 鍒嗛挓銆?- reviews parquet 濡傛灉宸茬粡瀛樺湪, 璺宠繃 reviews 涓嬭浇闃舵, 鐩存帴澶嶇敤銆?"""
import gzip
import json
import logging
import os
from collections import defaultdict
from typing import Iterable, Iterator
import pandas as pd
import requests
from tqdm import tqdm
from . import config as cfg
logger = logging.getLogger(__name__)
# McAuley Lab 瀹樻柟鐩撮摼妯℃澘
_BASE = "https://mcauleylab.ucsd.edu/public_datasets/data/amazon_2023/raw"
_REVIEW_URL_TMPL = _BASE + "/review_categories/{cat}.jsonl.gz"
_META_URL_TMPL = _BASE + "/meta_categories/meta_{cat}.jsonl.gz"
def _stream_jsonl_gz(url: str) -> Iterator[dict]:
"""Stream a gzipped JSONL file and yield parsed rows.
The function intentionally avoids downloading the full file to disk, so
sample mode can stop early after enough valid records have been collected.
"""
logger.info("GET %s", url)
headers = {"User-Agent": "Mozilla/5.0 (acsa_clothing downloader)"}
with requests.get(url, stream=True, headers=headers, timeout=(15, 60)) as r:
r.raise_for_status()
r.raw.decode_content = False
with gzip.GzipFile(fileobj=r.raw) as gz:
for raw_line in gz:
if not raw_line:
continue
try:
yield json.loads(raw_line)
except json.JSONDecodeError as e:
logger.warning("Skipping one invalid JSON line: %s", e)
continue
def _records_to_df(records: Iterable[dict]) -> pd.DataFrame:
return pd.DataFrame(list(records))
def _clean_meta_for_parquet(df: pd.DataFrame) -> pd.DataFrame:
"""鎶?meta 涓細璁?pyarrow 鎺ㄦ柇绫诲瀷澶辫触鐨勮剰鍊兼竻娲楁帀銆?
Amazon 鍏冩暟鎹噷 price 鏄?"鍙兘鏁板瓧 / 鍙兘 '鈥? / 鍙兘 'None' / 鍙兘绌? 鐨勬贩鍚堝瓧娈?
pandas 榛樿鎺ㄦ垚 object, pyarrow 钀?parquet 鏃跺嵈璇曞浘鎺ㄦ垚 double,
閬囧埌 '鈥? (em-dash) 杩欑闈炴暟瀛楀瓧绗︿覆灏辨姏 ArrowInvalid銆? 鎵€浠ヨ繖閲屾墜鍔ㄦ妸 price 寮哄埗杞垚 float64, 鏃犳硶瑙f瀽鐨勪竴寰嬪彉 NaN銆? """
if "price" in df.columns:
df["price"] = pd.to_numeric(df["price"], errors="coerce")
return df
def _backup_jsonl(records: list, parquet_path) -> str:
"""鍦ㄥ啓 parquet 鍓嶅厛钀戒竴浠藉師濮?jsonl 鍏滃簳銆?
璺緞灏辨槸 parquet 鍚屽悕鎹㈠悗缂€銆傚嵆浣垮悗缁?to_parquet 澶辫触涔熶笉蹇呴噸鐖€? """
raw_jsonl_path = os.path.splitext(str(parquet_path))[0] + ".jsonl"
os.makedirs(os.path.dirname(raw_jsonl_path) or ".", exist_ok=True)
with open(raw_jsonl_path, "w", encoding="utf-8") as f:
for row in records:
f.write(json.dumps(row, ensure_ascii=False) + "\n")
logger.info("Backup jsonl saved to %s (%d rows)", raw_jsonl_path, len(records))
return raw_jsonl_path
def download_sample(n_reviews: int = 30000, min_text_len: int = 20,
min_reviews_per_product: int = 2,
max_reviews_per_product: int = 5,
scan_multiplier: int = 5,
force: bool = False):
"""Sample 妯″紡: 娴佸紡璇诲彇鍓?N 鏉″彲鐢ㄨ瘎璁?鍐嶆媺瀵瑰簲 meta銆?
'Usable' 鎸? text 闈炵┖涓旈暱搴?>= min_text_len, 涓?parent_asin 闈炵┖銆? """
review_url = _REVIEW_URL_TMPL.format(cat=cfg.CATEGORY)
meta_url = _META_URL_TMPL.format(cat=cfg.CATEGORY)
# ===== Reviews 闃舵 =====
# 濡傛灉 reviews parquet 宸插瓨鍦?(涓婃璺戞垚鍔熶簡, 鏄?meta 闃舵宕╃殑),
# 鐩存帴璇诲嚭鏉ュ鐢?seen_asins, 璺宠繃杩欐閲嶆柊涓嬭浇銆?
if os.path.exists(cfg.RAW_REVIEWS_PATH) and not force:
logger.info("Reviews parquet 宸插瓨鍦? 澶嶇敤: %s", cfg.RAW_REVIEWS_PATH)
reviews_df = pd.read_parquet(cfg.RAW_REVIEWS_PATH)
seen_asins = set(reviews_df["parent_asin"].dropna().unique().tolist())
logger.info("浠庡凡鏈?reviews 涓仮澶?%d 鏉? %d 涓?unique parent_asins",
len(reviews_df), len(seen_asins))
else:
logger.info(
"Streaming reviews (grouped sample, target=%d, min_per_product=%d, max_per_product=%d, scan_multiplier=%d) from %s ...",
n_reviews, min_reviews_per_product, max_reviews_per_product, scan_multiplier, review_url,
)
candidate_target = max(n_reviews, n_reviews * max(scan_multiplier, 1))
candidates = []
pbar = tqdm(total=candidate_target, desc="review candidates")
for row in _stream_jsonl_gz(review_url):
text = (row.get("text") or "").strip()
if len(text) < min_text_len:
continue
if not row.get("parent_asin"):
continue
candidates.append(row)
pbar.update(1)
if len(candidates) >= candidate_target:
break
pbar.close()
by_asin = defaultdict(list)
for row in candidates:
by_asin[row["parent_asin"]].append(row)
eligible = {asin: rows for asin, rows in by_asin.items() if len(rows) >= min_reviews_per_product}
kept = []
for asin, rows in sorted(eligible.items(), key=lambda x: len(x[1]), reverse=True):
for row in rows[:max_reviews_per_product]:
kept.append(row)
if len(kept) >= n_reviews:
break
if len(kept) >= n_reviews:
break
if len(kept) < n_reviews:
used = {id(row) for row in kept}
for row in candidates:
if id(row) in used:
continue
kept.append(row)
if len(kept) >= n_reviews:
break
reviews_df = _records_to_df(kept[:n_reviews])
seen_asins = set(reviews_df["parent_asin"].dropna().unique().tolist())
per_product = reviews_df.groupby("parent_asin").size()
logger.info(
"Got %d reviews, %d unique parent_asins, avg_reviews_per_product=%.2f, products_with_%d_plus_reviews=%d.",
len(reviews_df), len(seen_asins), len(reviews_df) / max(len(seen_asins), 1),
min_reviews_per_product, int((per_product >= min_reviews_per_product).sum()),
)
reviews_df.to_parquet(cfg.RAW_REVIEWS_PATH, index=False)
# ===== Meta 闃舵 =====
# 鍐嶆媺 meta,鍖归厤 parent_asin
# 娉ㄦ剰: meta 娌℃湁淇濊瘉鎺掑簭,鐞嗚涓婅鎵畬鏁翠唤鎵嶈兘纭繚鍛戒腑鎵€鏈?asin銆?
# 浣?meta 姣?reviews 灏忓緱澶?(Clothing meta 鍑犵櫨 MB 鍘嬬缉),瀹屾暣鎵竴閬嶅彲鎺ュ彈銆? logger.info("Streaming meta to match parent_asins from %s ...", meta_url)
meta_kept = []
remaining = set(seen_asins)
pbar = tqdm(total=len(seen_asins), desc="meta")
for row in _stream_jsonl_gz(meta_url):
asin = row.get("parent_asin")
if asin in remaining:
meta_kept.append(row)
remaining.discard(asin)
pbar.update(1)
if not remaining:
break # 鍏ㄩ儴鍛戒腑,鎻愬墠鏂紑
pbar.close()
# 1) 鍏堣惤 jsonl 鍏滃簳, 鍝€曚笅闈?to_parquet 鐐镐簡涔熶笉蹇呴噸鐖?
_backup_jsonl(meta_kept, cfg.RAW_META_PATH)
# 2) 鍐嶅仛娓呮礂 + 鍐?parquet
meta_df = _records_to_df(meta_kept)
logger.info(
"Got %d meta entries (matched %.1f%% of review asins).",
len(meta_df),
100 * len(meta_df) / max(len(seen_asins), 1),
)
meta_df = _clean_meta_for_parquet(meta_df)
meta_df.to_parquet(cfg.RAW_META_PATH, index=False)
return reviews_df, meta_df
def download_full():
"""Download the full review and metadata tables. Very large; use with care."""
review_url = _REVIEW_URL_TMPL.format(cat=cfg.CATEGORY)
meta_url = _META_URL_TMPL.format(cat=cfg.CATEGORY)
logger.info("Downloading FULL reviews from %s ...", review_url)
reviews = []
for row in tqdm(_stream_jsonl_gz(review_url), desc="reviews"):
reviews.append(row)
reviews_df = _records_to_df(reviews)
logger.info("Reviews: %d rows", len(reviews_df))
reviews_df.to_parquet(cfg.RAW_REVIEWS_PATH, index=False)
logger.info("Downloading FULL meta from %s ...", meta_url)
metas = []
for row in tqdm(_stream_jsonl_gz(meta_url), desc="meta"):
metas.append(row)
# 钀?jsonl 鍏滃簳
_backup_jsonl(metas, cfg.RAW_META_PATH)
meta_df = _records_to_df(metas)
logger.info("Meta: %d rows", len(meta_df))
meta_df = _clean_meta_for_parquet(meta_df)
meta_df.to_parquet(cfg.RAW_META_PATH, index=False)
return reviews_df, meta_df
|