AI 资讯
Craigslist's JSON-LD has no ID field — we join 290 of 325 listings by title alone
Quick answer Craigslist search pages ship two copies of every listing: a static HTML list, and a JSON-LD <script> block with images, currency, and geo-coordinates. The obvious move is to join them by ID. Don't — Craigslist's JSON-LD carries no shared identifier at all , not a bare post ID, not a URL, not a SKU. The only field both copies reliably share is the listing's title, and titles repeat. We joined by title through a per-title FIFO queue and measured it recovering 290 of 325 listings (89%) end-to-end on a captured 298-item page. That number is the ceiling of what a title-only join can do on this page shape — plan your field completeness around it, don't assume 100%. Why can't you just match the JSON-LD by ID? 🧩 When we built the Craigslist Multi-City Listings Scraper , the first design assumed what almost every JSON-LD block on almost every e-commerce-shaped site provides: a productID , a sku , a url , an @id — something that lines up a JSON entry with its DOM counterpart deterministically. Live inspection of a captured Craigslist search page found none of those. Each itemListElement entry has exactly name , image , offers , @type , and a position field that looks like it should solve the problem — until you check it against the static list past the first ~18 entries, where some static-list rows have no JSON-LD counterpart at all and the position numbering drifts out of alignment. So the join key that's actually usable, live, is title — and titles aren't unique. The fix is a FIFO queue per title: walk the static <li> list in document order, and for each title pop the next unconsumed JSON-LD entry with a matching name. # actors/craigslist-listings-scraper/src/search_parser.py def _parse_ld_json ( tree : HTMLParser ) -> dict [ str , deque [ _LdEntry ]]: by_title : dict [ str , deque [ _LdEntry ]] = defaultdict ( deque ) for list_item in data . get ( " itemListElement " , []): entry = _ld_entry_from_item ( list_item ) title = list_item . get ( " item " , {}). get
AI 资讯
BBB.org's business data isn't in the HTML — it's in an analytics script tag
Quick answer BBB.org doesn't render business data into the HTML table it looks like it does. Both the search-results page and every business profile page embed the real data as one inline JSON blob — webDigitalData — sitting inside a <script> tag meant for analytics, not for you. The visible <dt> / <dd> list underneath it only carries the fields the analytics layer left out (accreditation date, years in business). If you scrape the DOM table and ignore the script tag, you'll get a name and maybe a phone number and nothing else. If you scrape the script tag and ignore the DOM, you'll get ratings and IDs but no address, no years-in-business, no website. You need both, merged, per business. Why does the DOM only have half the data? 🕵️ When we built the BBB Business Leads Scraper , the first pass assumed BBB profile pages worked like most directory sites: a template with labeled fields you css_first() your way through. That assumption survives for exactly four fields — address, accreditation date, years in business, and website — which live nowhere except a <dt> / <dd> definition list further down the page. Everything that actually matters for lead scoring — business name, BBB letter grade, accreditation status, phone number, the internal IDs BBB uses to build its own canonical URL — comes from a different place entirely: a webDigitalData object, wired into the page for BBB's own analytics vendor, that happens to be complete, well-formed JSON: # actors/bbb-business-leads-scraper/src/parsers/common.py WEB_DIGITAL_DATA_MARKER = " webDigitalData " def extract_web_digital_data ( html : str ) -> dict | None : marker_pos = html . find ( WEB_DIGITAL_DATA_MARKER ) if marker_pos == - 1 : return None brace_start = html . find ( " { " , marker_pos ) raw = _extract_balanced_json ( html , brace_start ) ... return json . loads ( raw ) That is not a regex grabbing {.*} between two markers — a naive non-greedy match snaps shut on the first stray } inside a nested object, which this blo
AI 资讯
BVH for Collision Detection: From AABB to Optimal Hierarchies
Table of Contents Why Broad-Phase Exists (and why naive O(N²) dies at 10k objects) Bounding Volume Hierarchy: The Data Structure That Scales Topology Choices: Binary vs. Multi-Branch, Pointer vs. Array Layout Construction Algorithms: From Naive to SAH-Optimal Traversal Strategies for Collision Queries The Static/Dynamic Dichotomy: Why One Tree Cannot Serve Two Masters The Dual-BVH Architecture Preview 1. Why Broad-Phase Exists The Pairwise Problem Every collision detection system faces the same fundamental challenge: given N objects, determine which pairs might be colliding so the expensive narrow-phase (SAT, GJK, EPA) only runs on plausible candidates. The naive approach tests every pair: // Naive O(N²) broad-phase — dies at ~10k objects std :: vector < CollisionPair > broadPhaseNaive ( const std :: vector < Object *>& objects ) { std :: vector < CollisionPair > pairs ; for ( size_t i = 0 ; i < objects . size (); ++ i ) { for ( size_t j = i + 1 ; j < objects . size (); ++ j ) { if ( aabbOverlap ( objects [ i ] -> aabb , objects [ j ] -> aabb )) { pairs . emplace_back ( objects [ i ], objects [ j ]); } } } return pairs ; } Complexity: O ( N ² ) AABB tests. At 60 Hz you have 16.67 ms/frame. At 120 Hz: 8.33 ms. Objects (N) Pairwise Tests @ 3 ns/test Frame Budget (60 Hz) 100 4,950 0.015 ms Trivial 1,000 499,500 1.5 ms Comfortable 10,000 49,995,000 150 ms 10x over budget 100,000 ~5x10^9 15,000 ms Impossible Cache Miss Catastrophe The pairwise loop doesn't just do too much work, it does it poorly . Each iteration accesses two random objects in memory. With 10k objects, you're thrashing L3 cache every frame. The BVH approach exploits spatial coherence: nearby objects in space are nearby in the tree, turning random access into sequential scans. The Real Job: Proving Separation KEY INSIGHT: Broad-phase is a rejection machine. Broad-phase is not about finding collisions. It's about proving separation as cheaply as possible. Every AABB overlap test that returns false is a vic
AI 资讯
RSA-2048 and RSA-3072 have different futures
Published 2026-09-06 This describes NIST IR 8547 ipd — the initial public draft of November 2024 — as it stood on the date above. Its dates are proposed. If a final IR 8547 has published since you are reading this, check it: the numbers below may have moved, and the article's central caveat may no longer apply. If you have read anything about post-quantum migration in the last year, you have read that RSA is deprecated in 2030. It is one of those facts that has been repeated into the shape of a rule. It is half true, and the half that is false is the half people plan around. Here is the actual table, from NIST IR 8547, Transition to Post-Quantum Cryptography Standards : algorithm parameters transition RSA, ECDSA 112 bits of security strength Deprecated after 2030 Disallowed after 2035 RSA, ECDSA, EdDSA ≥ 128 bits of security strength Disallowed after 2035 Read the second row again. At 128 bits and above there is no 2030 row at all . RSA-3072 is not deprecated in 2030. Neither is P-256. The 2030 date applies to 112-bit strength — RSA-2048, P-224, 2048-bit finite-field Diffie-Hellman — and to nothing else. The key-establishment table (Table 4, covering finite-field DH/MQV, elliptic curve DH/MQV and RSA) has exactly the same shape. Same split, same dates. So an inventory that reports "47 uses of RSA, all deprecated in 2030" is reporting something the document does not say. Some of those uses are on a 2030 clock and some are on a 2035 clock, and which is which depends on a field most tooling does not look at. Deprecated is not disallowed The second thing worth getting right is what the two words mean, because they are not synonyms and NIST defines both: deprecated — "The algorithm and key length may be used, but the user must accept some security risk." disallowed — "The algorithm or key length is no longer allowed for applying cryptographic protection." Deprecated is a risk acceptance. You may continue, with your eyes open and presumably a note in a register somewhere.
开发者
Two Bugs Later: What It Actually Took to Replace a DNS Library
A library isn't code. A library is thirty or forty decisions somebody already made, correctly,...
AI 资讯
Scraping 150k+ Instagram followers reliably: batching, resume-on-error, and enrichment
I run a small AI/automation consultancy in Brazil, and a recent lead-research project needed the full follower list of a public Instagram profile — about 153,000 followers — plus enrichment (bio, public email/phone) to find business accounts worth contacting. The problem Pulling a list that size is never one API call. Instagram reports ~153,628 followers; you get them page by page, and any long-running extraction WILL hit a failed request eventually. If your pipeline can't resume, you start over from zero — which is expensive and slow. What I built The pipeline runs on n8n with Supabase as the datastore: Batched extraction — followers are downloaded in batches of up to 10,000 per cycle, on a schedule, instead of one giant run. Resume on error — every page cursor and count is persisted. When a request fails mid-run (in one run it stopped at 4,782 followers after 96 pages read), the job logs the error, emails me a status report, and picks up from the same point on the next cycle instead of restarting. Enrichment pass — a second workflow walks the stored followers and pulls profile details, flagging commercial accounts and any public email/phone in the bio. Personal/private accounts return no contact data, which the report counts separately. Email reports — each cycle sends me a summary: profile, followers reported vs. downloaded, pages read, batch name, and the exact error if one occurred. For the Instagram data layer I used HikerAPI — I tested a few other options first, and it won on pricing and rate limits for this volume. It handled the pagination fine: the run above made 100+ requests without me managing sessions or proxies myself. Tradeoffs / what didn't go perfectly Long extractions still fail sometimes (timeouts); resume logic is not optional at this scale, whatever API you use. Early days for me on this stack: so far it has worked well, but I'm still collecting more data before I'd call the pipeline battle-tested. I'll know more after a few full 150k-follower
AI 资讯
Local Business Lead Scrapers on Apify Compared (September 2026)
Most local business lead scrapers on Apify are Google Maps scrapers with a website-crawling step bolted on. lukaskrivka/google-maps-with-contact-details is the most used (87,957 users, 4.63 stars). flash_scraper/local-business-leads is the outlier: it discovers businesses on OpenStreetMap instead of Google Maps, and includes MX email verification in its $3 per 1,000. Every figure below was read from Apify's public Store API ( GET /v2/store ) on 2026-09-05 — including every user count, so they are all on the same footing. The per-actor endpoint ( GET /v2/acts/<id> ) can read one higher: it gives flash_scraper/local-business-leads 33 rather than 32, and code-node-tools 33 as well. Prices, users and ratings change; the Pricing tab on each actor page is authoritative. Disclosure: I publish flash_scraper/local-business-leads , one of the actors compared here. Its limits are listed in the same detail as everyone else's, including the one that will disqualify it for many buyers. How prices are normalised These actors bill per event, and the events differ in kind, which makes headline prices misleading. Some charge per place found. Some charge separately for the website crawl that actually produces the email. Some charge again to verify that the email is deliverable. The table lists the primary per-result event multiplied by 1,000 at the free-plan rate , then names the add-on events, because a $5 per 1,000 place price with a $100 per 1,000 email-verification add-on is not a $5 tool. Paid Apify plans get tiered discounts on several of these actors, ours included — and on the add-on events the discount can be enormous. lukaskrivka's three $100-per-1,000 add-ons fall to $4.00 (email verification), $7.50 (lead enrichment) and $10.00 (social-profile enrichment) per 1,000 on Bronze, and lower again above it (Store pricing record read 2026-09-05). Our own free-plan-to-Diamond spread is about 30 percent. So if you are on a paid plan, re-read every figure below off the Pricing tab:
AI 资讯
How We Built Perceive: Web Content Extraction for RAG Pipelines
A browser and a language model can look at the same URL and effectively see two different things. A browser sees a rendered interface: navigation, cookie banners, buttons, ads, sidebars, images, scripts, interactive components, and eventually the text a human came to read. A language model sees whatever representation we decide to give it. That distinction matters when the URL is going into a RAG pipeline. Open the developer tools on any major news or documentation site and look at the raw HTML. A typical article page runs between 300KB and 800KB of markup. The article text itself is usually between 2KB and 10KB. The ratio of markup to content is consistently between 10:1 and 40:1 depending on how heavily templated the site is. When you pass raw HTML to a language model, you are passing all of it, and most pipelines treat this as an acceptable default. Perceive is the endpoint we built to fix that. You give it a URL. It returns clean Markdown. This post is about what happens in between and why we made the engineering decisions we did. Why raw HTML is a poor RAG input The token waste is real but it is not the worst problem. Three failure modes compound each other. Token waste . A blog post with 800 words of real content can run to 6,000–12,000 tokens as raw HTML once you include navigation, scripts, inline styles, and layout markup. The same content in Markdown is often 900–1,200 tokens. That is not just a cost issue. It is context window space that cannot go to content. Embedding contamination . Embedding models are trained predominantly on natural language. When you embed a chunk containing <div class="sidebar-widget__title">Related Articles</div> alongside the article content, the vector is pulled toward the markup semantics rather than the content semantics . The embedding does not cleanly represent the article; it represents a mixture of the article and the site's component naming conventions. Retrieval degrades as a result: chunks that should be semantically si
开发者
Pentagon rescinds new testosterone screening policy without explanation
The Pentagon says the guidance is being updated after being online for one day.
AI 资讯
Frappe Framework Explained: How an Open-Source Framework Can Power Custom Business Applications
When businesses outgrow spreadsheets and disconnected SaaS tools, the next question is often whether they should buy another application, customize an existing platform, or build a system specifically around their workflows. For many organizations, building custom business software can appear expensive and technically demanding. A development team has to think about authentication, permissions, database models, APIs, user interfaces, background jobs, reporting, audit trails, and deployment. This is where open-source application frameworks can change the equation. Instead of building every foundational capability from scratch, a framework can provide the underlying architecture while developers focus their effort on the business problems that actually differentiate the organization. One example is the Frappe Framework , an open-source web application framework used to build business applications such as ERPNext. But what exactly is Frappe, and why would an organization consider using it for custom enterprise software? What Is Frappe Framework? Frappe is an open-source, Python- and JavaScript-based web application framework designed to make it easier to build database-driven business applications. Rather than being simply a collection of programming utilities, Frappe provides a broader application foundation. It includes capabilities for: Data modeling Authentication Role-based permissions REST APIs Web forms Background jobs Reporting Workflow management Notifications File attachments Activity and audit information User interfaces Database access Application configuration This means a development team can start with an application architecture that already understands many of the requirements common to business software. The important distinction is this: Frappe is a framework for building applications. ERPNext is an application built using that framework. That distinction matters when evaluating Frappe for custom software development. Frappe vs ERPNext Frappe and ERP
AI 资讯
I Built a Full IT Ticket System in Power Apps — Here's the SLA Engine That Runs Without Power Automate
I recently built a complete IT ticket management system in Power Apps — 9 screens, role-based access, live SLA tracking, and automatic email notifications. The part I want to actually talk about here isn't the UI, it's the SLA engine, because I built it to work without Power Automate , and the trick is simpler than it looks. The problem SLA tracking normally means: a ticket is "Critical" → 60 minute target → somebody needs to know if it's about to breach or already has. The obvious way to do this is a scheduled Power Automate flow that checks every ticket on a timer and flags the ones in trouble. I wanted this app to run on Power Apps collections only — no flow, no external data source — so a scheduled flow wasn't an option. The question was: can you get "live" SLA status without a background job? The trick: recalculate on every read, not on a timer Instead of a flow updating a SLAStatus field periodically, I recalculate it every time the app or a screen is opened, using Now() against the stored due date: \ UpdateIf( colTickets, Status <> "Resolved" && Status <> "Closed", { SLAStatus: If( Now() > DueDate, "Breached", DateDiff(Now(), DueDate, TimeUnit.Minutes) <= SLAMinutes * 0.2, "At Risk", "On Track" ) } ); UpdateIf(colTickets, Status = "Resolved" || Status = "Closed", {SLAStatus: "Met"}) \ \ This runs in App.OnStart , at the top of every screen's OnVisible , and behind a manual "Refresh SLA" button. The At Risk threshold is 20% of the SLA window remaining — so a Critical ticket (60 min target) goes At Risk with 12 minutes left; a Low ticket (1440 min / 24 hrs) goes At Risk with 4.8 hours left. The honest tradeoff: this only updates when someone has the app open. A ticket breaching at 2am with nobody looking won't trigger anything until the next visit. For a real production deployment I'd pair this with a scheduled flow for after-hours detection — but for a demo, an internal tool with regular traffic, or anything where "eventually consistent within the next visit"
AI 资讯
Sealing a file so nobody can argue you touched it
An argument about a digital file is almost never lost over what the file says. It is lost one question earlier: How do we know that is the file you received, and not the one you edited last night? If the answer is "trust me", you have already lost. However right you are on the substance. This problem is not exclusive to a courtroom. The auditor receiving a log dump has it. So does the team documenting an incident, or anyone keeping a copy of a contract signed over email. In every case the need is the same: being able to prove that a set of bytes has not changed since a given moment — and having that proved by someone who is not you . That is why I wrote Tunjo : a Rust tool that walks material read-only, computes its fingerprint, and signs a record anyone can verify. Why a tree and not a hash The obvious approach would be to concatenate everything and take one SHA-256. It works, and it is useless in practice. When someone disputes one file — a specific email out of four thousand — a single hash leaves you two options: hand over the complete set so it can be recomputed, or ask to be believed. The first exposes material that has no business being exposed; the second is not evidence. A Merkle tree solves exactly that. Each file is a leaf, each pair of nodes combines upward, and a root remains. To prove a leaf belongs to that root, you only need to show that leaf and the path of hashes to the top: a few kilobytes. The rest of the set is never touched. Two details of the tree that are not optional: // Domain separation: a leaf can never pass itself off as an internal node. h .update ([ 0x00 ]); // leaf h .update ([ 0x01 ]); // internal node // And the root binds the number of leaves. h .update ([ 0x02 ]); h .update ( n .to_be_bytes ()); Without the first, a leaf hash could be presented as if it were a node of the tree. Without the second you get the classic ambiguity of trees with an odd number of leaves: two different sets can produce the same root. It is an old, well-kn
AI 资讯
Reverse Proxies vs Forward Proxies: Which Architecture Do You Need?
Introduction When you're scaling infrastructure or managing network security, proxies become essential tools—but they solve fundamentally different problems. A reverse proxy sits between your users and your backend servers, while a forward proxy sits between your users and the internet. This distinction might sound academic, but it shapes your entire architecture: from load balancing and security posture to compliance requirements and cost structures. Choosing the wrong proxy type can lead to bottlenecks, security vulnerabilities, or unnecessary infrastructure complexity. This article walks you through real-world scenarios, pricing considerations, and decision frameworks to help you deploy the right solution. Forward Proxies: Controlling Outbound Traffic What Forward Proxies Do A forward proxy intercepts requests from your internal network and forwards them to external servers on the internet. From the external server's perspective, the proxy is the client—the real origin of the request is masked or modified. Common use cases include: Employee internet access control : A company deploys a forward proxy so IT can block malicious domains, filter content, and enforce acceptable use policies Data residency compliance : A financial services firm routes all outbound API calls through a forward proxy in a specific geographic region to meet regulatory requirements Web scraping at scale : When extracting data from multiple websites, forward proxies rotate request sources to avoid IP-based blocking DDoS mitigation for outbound traffic : Distributed request aggregation through a forward proxy can reduce fingerprinting risks Pricing and Infrastructure Costs Forward proxies typically charge per: Concurrent connections : Enterprise solutions like Zscaler or Palo Alto Networks start around $5–15 per user/month Data transferred : Cloud-based forward proxies charge $0.05–$0.30 per GB, depending on geography and provider IP rotation : Proxy services offering residential IPs (for non-
AI 资讯
FreshCtx 0.6.0: Stop AI agents from acting on stale data
AI agents do not need to hallucinate to make the wrong decision. They can read accurate information, reason correctly, and still take the wrong action because the information changed before execution. That is the problem FreshCtx is built to address. The same failure keeps appearing in different systems Developer feedback around FreshCtx surfaced several versions of the same underlying problem: A subscription status changed in Stripe, but an application acted on its old snapshot. A deployment worker continued after another worker had already claimed the job. An agent relied on remembered database action items instead of checking their current status. A research source changed after a claim had been prepared. A voice workflow reached an outdated business record after correctly understanding the request. Different industries and different tools, but the same gap: The reasoning was valid when produced, but stale when executed. What changed in FreshCtx 0.6.0 FreshCtx now provides the same pre-action freshness boundary across several practical environments: Stripe Subscription validation An Agno pre-tool integration Synchronous LangGraph action-node wrappers Asynchronous LangGraph action-node wrappers Selective revalidation of only the evidence an action declared Audit evidence explaining why an action was allowed or blocked The LangGraph integration checks the evidence an action depends on immediately before the node runs. If a required dependency changed or cannot be verified, FreshCtx blocks before the node body starts. FreshCtx does not replace LangGraph routing, retries, checkpointing, transactions, or idempotency. It adds the missing freshness check at the point where reasoning becomes action. Why framework neutrality matters Agno and LangGraph have different execution models. Stripe is not an agent framework at all. The integration changes, but the control remains consistent: An action declares the evidence it depends on. FreshCtx checks that evidence again at the
AI 资讯
Hybrid encryption: why combine classical and post-quantum cryptography
When a new cryptographic algorithm appears, a tension shows up: classical algorithms such as X25519 or Ed25519 have resisted attacks for years, but are vulnerable to a future quantum computer; post-quantum ones such as ML-KEM or ML-DSA resist quantum attacks, but are newer and less tested. Hybrid encryption resolves the tension: use both at once . The idea in one sentence Combine a classical and a post-quantum algorithm so that the system only breaks if both fail simultaneously . A classical attacker would have to break the post-quantum algorithm; a quantum attacker would have to break the classical one and the post-quantum one. You gain security against the future without betting everything on a young algorithm. Two places to apply it Key exchange (encrypting for a recipient). You combine: X25519 — classical key exchange, fast and heavily tested. ML-KEM-1024 — NIST's post-quantum key encapsulation mechanism, at its highest level. The two resulting keys are mixed with a context-bound derivation function (HKDF), so that neither one alone is enough. Digital signatures (authenticity). You combine: Ed25519 — classical signature. ML-DSA-87 — NIST post-quantum signature. The message is accepted only if both signatures verify — an AND combiner. One principle that never breaks There is a golden rule in cryptography, Kerckhoffs's principle : a system must be secure even if the attacker knows its entire design; security lives in the key , not in hiding the format. A good hybrid system uses public, audited primitives — XChaCha20-Poly1305 to encrypt, Argon2id to derive keys from passwords, HKDF to separate domains — and never invents its own cryptography . How Quipu applies it Quipu is a free library implementing exactly this approach for data at rest : hybrid X25519 + ML-KEM-1024 encryption, hybrid Ed25519 + ML-DSA-87 signatures, and only verified primitives underneath. It targets NIST security level 5 (CNSA 2.0) and is open source, so anyone can review how it works. An honest
AI 资讯
I Replaced grep-Based Code Review with a Knowledge Graph + MCP. Here Are 3 Bugs Vector Search Missed.
For about a year, my AI code review setup looked like this: AI gets a PR, AI greps for related code, AI reads way too many files, AI says "looks fine." It mostly worked. Until the bugs that didn't show up in grep started shipping. The problem wasn't the model. It was the retrieval. Vector search and keyword grep are great at finding files that mention auth.py . They're terrible at finding files that depend on auth.py through three import hops, an event bus, and a decorator. That's where the bugs live. I rewired the retrieval layer with a code knowledge graph plugged in through MCP. Three bugs surfaced in the first week that vector search had been quietly missing. Here's what changed and the bugs themselves. Why grep + vector search missed these Vector search retrieves by semantic similarity . "Find code about authentication" finds auth.py , login.py , password_validator.py . Useful. Knowledge graphs retrieve by structural relationship . "What depends on auth.py ?" returns the call graph -- including event_handlers/login_event.py , which never mentions auth in its variable names but listens to a login event whose payload changes when auth.py changes. Both are valid. They answer different questions. The bugs that ship to production tend to live in the second question. The setup: code KG as an MCP server The Model Context Protocol (MCP), released by Anthropic in late 2024, lets you expose tools to a model in a standard way. By 2026 it's supported by Claude Code, Cursor, Windsurf, Zed, VS Code, and (as of GA in May 2025) the official MCP Registry hosts hundreds of servers. I used code-review-graph , an open-source tool that builds a property graph of your codebase and exposes it as an MCP server. The setup is a three-line ritual: pip install code-review-graph code-review-graph build ./my-project code-review-graph install # auto-detects Claude Code / Cursor / Windsurf The graph contains nodes for files, classes, functions, and tests, with edges for imports, calls, inheri
AI 资讯
Quipu: post-quantum encryption in pure Rust, with a Python wheel
Protecting data that must stay secret ten years from now is a problem for today : an adversary can capture your encrypted traffic now and decrypt it once quantum capability exists ( harvest now, decrypt later ). Quipu is a free hybrid post-quantum encryption library for data at rest: it combines proven classical cryptography with the new kind, so that it only breaks if both fall at once. Pure Rust, and why Quipu started out aiming at several languages: a Rust core with a C ABI on top and bindings for Python, Node and Go. It worked, but the lesson was clear: maintaining a stable C interface plus four bindings, each with its own packaging and interoperability tests, was complexity that did not pay for itself against the real goal — protecting data at rest — and it widened the attack surface with unsafe we did not want. Today Quipu is pure Rust : memory safe, no garbage collector, no first-party unsafe . And for people who do not write Rust, it ships as a native Python wheel via PyO3 — the surface that non-Rust users actually need. One codebase, one thing to audit. It is the same philosophy that guides the rest: where good cryptography exists, reuse it; simplicity is a security decision, not a convenience. Installation cargo add quipu # Rust pip install quipu-crypto # Python (native wheel, PyO3) Encrypt and decrypt in Python import quipu # Symmetric, with a passphrase blob = quipu . encrypt_stream ( b " sensitive data " , " my-passphrase " ) assert quipu . decrypt_stream ( blob , " my-passphrase " ) == b " sensitive data " # Post-quantum, for a recipient pub , sec = quipu . generate_keypair () # X25519 + ML-KEM-1024 c = quipu . encode_to_recipient ( b " secret " , pub ) assert quipu . decode_as_recipient ( c , sec ) == b " secret " What is underneath Encryption: XChaCha20-Poly1305 (authenticated AEAD). Key derivation: Argon2id (brute-force resistant) + HKDF. Post-quantum: X25519 + ML-KEM-1024 for keys; Ed25519 + ML-DSA-87 for signatures. Security level: NIST category 5
AI 资讯
The Most Important AI Agent Design Choice: Don’t Let the Model Be the Final Authority
AI agents are getting very good at doing things . They can search databases, call APIs, modify tickets, draft code, update records, trigger workflows, and interact with production systems. And that changes the engineering problem. When an LLM only generates text, a bad answer is usually just that: a bad answer. When an LLM can take an action, a bad answer can become a bad state change . So the most important question in agent architecture is no longer: Can the model figure out what to do? It is: Who decides whether the model should actually be allowed to do it? Those are two very different responsibilities. And I think one of the most useful principles for production AI agents is surprisingly simple: Use the model to reason. Don’t automatically give it authority to execute. The architecture that works beautifully in demos A lot of agent demos reduce to something like this: User → LLM → Tool → Action The model receives a request. It reasons about what should happen. It selects a tool. It generates the parameters. The tool executes. That is an incredibly productive abstraction. It is also a risky one when the tool can affect something real. The same probabilistic system is effectively doing two jobs: deciding what it believes should happen; authorizing that thing to happen. You can try to fix this with prompting: Always ask for confirmation before making important changes. But that is still an instruction. It is not a security boundary. The difference becomes clearer when you compare the two architectures. %%{init: {'theme':'base','themeVariables': { 'primaryTextColor':'#111827', 'secondaryTextColor':'#111827', 'tertiaryTextColor':'#111827', 'textColor':'#111827', 'edgeLabelBackground':'#FFFFFF', 'lineColor':'#4B5563' }}}%% flowchart LR subgraph BAD["❌ Demo-Style Agent"] direction LR A["User"] --> B["🧠 LLM"] B --> C["🔧 Tool"] C --> D["💥 Real-World Action"] end subgraph GOOD["✅ Production-Oriented Agent"] direction LR E["User"] --> F["🔎 Evidence"] F --> G["🧠 LLM"] G --
AI 资讯
Panasonic Lumix L10 review: A stylish and capable compact camera
The LX100 II successor offers good speed and image quality at a fairly high price.
AI 资讯
Building an Enterprise Football Data Pipeline: Decoding Flashscore's Protocol for xG & Referee Analytics
Most football data scrapers on the market only extract high-level final scores (e.g. 2-1 ). But quantitative sports analysts, data scientists, and predictive betting modelers need granular data: Expected Goals (xG) , Official Referee Assignments , Goal Scorers paired with Assist Providers , and Half-Time vs Full-Time (1H/2H) statistical breakdowns . When I set out to build a professional-grade Flashscore scraper on Apify, I ran into two major engineering challenges: The Memory Problem : Keeping Puppeteer running to scrape hundreds of historical matches consumes over 1.5GB of RAM per run. The Protocol Problem : Flashscore serves its deep statistical feeds using a proprietary pipe-delimited data format ( ~ , ¬ , ÷ ) over CDN endpoints, rather than standard REST APIs. In this tutorial, I'll explain how I engineered the Flashscore Elite Statistics Extractor , how the hybrid Browser + HTTP/2 streaming pipeline drops RAM footprint from 1.5GB to 70MB , how to parse Flashscore's custom feed protocol, and how to pipe the resulting datasets directly into Python and Pandas. 🏛️ The Hybrid Pipeline Architecture To achieve zero proxy reliance for standard runs and ultra-low compute costs, the Actor splits execution into a 2-Phase Hybrid Pipeline : [ League & Season Selection ] │ ▼ ┌───────────────────────────────────────────┐ │ Phase 1: Browser Handshake (Puppeteer) │ │ - Captures x-fsign security tokens │ │ - Extracts countryId & tourId │ └─────────────────────┬─────────────────────┘ │ [ Immediate Browser Shutdown ] (RAM drops from 1.2GB -> 70MB) │ ▼ ┌───────────────────────────────────────────┐ │ Phase 2: Parallel HTTP/2 Feed Workers │ │ - got-scraping with JA3 TLS matching │ │ - Decodes df_st_1_ (Stats) & df_sui_1_ │ └─────────────────────┬─────────────────────┘ │ ▼ ┌───────────────────────────────────────────┐ │ Self-Healing Recovery Pass │ │ - Auto-retries skipped/failed matches │ └─────────────────────┬─────────────────────┘ │ ▼ ┌───────────────────────────────────────────┐