7.6 KiB
PPnix Spider Design
Goal
Implement a new Python spider at py/PPnix.py for PPnix based on the provided Node reference, adapted to this repository's existing Spider interface and testing conventions.
Scope for this iteration:
- Support two categories: movie and tv
- Expose stable filters only: type/class and sort/by
- Parse homepage recommendations
- Parse category listings
- Parse keyword search
- Parse detail pages and playlist data
- Return direct playback URLs from PPnix m3u8 endpoints
Explicitly out of scope for this iteration:
- Dynamic filter discovery and caching
- TVBox-specific
data:m3u8rewriting - Subtitle aggregation in
playerContent - JavaScript server route parity with the Node plugin
Target API Shape
The spider will implement the standard methods used in this repo:
homeContent(filter)homeVideoContent()categoryContent(tid, pg, filter, extend)searchContent(key, quick, pg="1")detailContent(ids)playerContent(flag, id, vipFlags)
The spider will follow current repo conventions:
- Single-file site adapter
- Deterministic parsing helpers
- Short site-local IDs rather than full URLs
- No
pagecountfield in list/search responses
Data Model
Categories
Use stable repo-style numeric category IDs instead of the Node plugin's string IDs:
1=> movie2=> tv
Display names:
1=>电影2=>电视剧
Filters
Only expose stable filters:
classby
For both category IDs, homeContent will return:
class: values extracted from the Node reference and hard-coded per categoryby:time,hits,score
Sort mapping for request URLs:
time=>newstimehits=>onclickscore=>rating
Default sort:
time
filter_def caching is unnecessary because filters are static.
IDs
To match repository guidance, the spider will keep IDs short.
- List/detail
vod_id: relative detail path such asmovie/123.htmlortv/456.html - Play IDs: compact encoded payload containing only
infoIdandparam
Recommended play ID format:
infoId|urlencoded(param)
This is sufficient because the final playback URL is deterministic:
https://www.ppnix.com/info/m3u8/{infoId}/{encoded_param}.m3u8
No referer or display name needs to be stored in the ID for this scope.
Architecture
PPnix.py will keep logic in a single class with focused helpers.
Core helpers
-
_build_url(path)- Normalizes relative paths against
https://www.ppnix.com - Supports raw absolute URLs and root-relative paths
- Normalizes relative paths against
-
_request_html(path_or_url, referer=None, extra_headers=None)- Performs GET requests through
fetch - Applies browser-like headers
- Returns response text or empty string on non-200
- Performs GET requests through
-
_clean_text(value)- Strips HTML whitespace noise and entities used in visible text
-
_fix_image(url)- Converts relative poster URLs to absolute URLs
List/category/search helpers
-
_map_type_slug(tid)1 -> movie,2 -> tv
-
_build_category_url(tid, pg, extend)- Converts repo category/filter input into PPnix path format
- First page omits the page index segment
- Path format:
/cn/{type_slug}/{genre}---{page_index}-{sort}.html
- For this scope, unsupported filters are ignored
-
_build_search_url(keyword, pg)- Uses the Node reference pattern:
/cn/search/{encoded_keyword}--.html- page > 1 appends
-page-{pg}
- Uses the Node reference pattern:
-
_parse_cards(html, type_hint="")- Parses list cards from homepage/category/search HTML
- Extracts:
vod_idvod_namevod_picvod_remarks
- Filters out malformed entries
- Deduplicates by
vod_id
Detail/play helpers
-
_extract_m3u8_items(html)- Reads:
infoid = 123m3u8 = [...]
- Returns
infoIdplus ordered episode/item labels
- Reads:
-
_build_play_id(info_id, param)- Returns compact playback ID
-
_parse_play_id(play_id)- Safely reverses the compact playback ID
-
_parse_detail_page(html, vod_id)- Extracts title, poster, year, director, actor, content
- Determines
type_namefromvod_id - Builds a single
PPnixplay group using the extracted m3u8 items
Request and Parsing Flow
homeContent
Returns static categories and static filters only.
homeVideoContent
Requests /cn/, parses homepage blocks, merges movie and tv cards, and caps to 20-24 items. The implementation should prefer deterministic extraction over mirroring every homepage section.
categoryContent
- Build category URL from
tid,pg, andextend - Fetch category page
- Parse cards
- Filter cards so returned
vod_idmatches the requested type slug - Return:
pagelimittotallist
total can follow the repo’s common approximate pattern:
page * limit + len(list)
This is acceptable because the site does not expose a clean total count in the provided reference.
searchContent
- Build search URL
- Parse cards from search result page
- Keep only IDs matching
movie/<id>.htmlortv/<id>.html - Return
page,limit,total,list
detailContent
- Normalize incoming ID into a relative detail path
- Fetch detail page
- Parse metadata
- Parse
infoidandm3u8item names from inline JS - Build:
vod_play_from = "PPnix"if episodes existvod_play_url = "名称$playId#名称$playId..."
playerContent
- Decode
play_idintoinfoIdandparam - If either is missing, fall back to parse-required response
- Build direct source URL:
https://www.ppnix.com/info/m3u8/{infoId}/{encoded_param}.m3u8
- Return direct-play payload with headers:
Referer: https://www.ppnix.com/cn/Origin: https://www.ppnix.comUser-Agent: browser UA
No m3u8 text rewriting will be attempted.
Error Handling
- Request helpers return empty string on non-200 to keep parser code simple
- Parser helpers should tolerate missing nodes and return empty/default fields
detailContentreturns{"list": []}on fetch/parse failureplayerContentreturns parse-required fallback whenplay_idis malformed
This matches the current repo’s defensive style better than raising exceptions.
Testing Strategy
Tests will be added at py/tests/test_PPnix.py using unittest and unittest.mock.
Red tests to write first
homeContentreturns categories1and2, and only stable filter keys_build_category_urlmaps default and selected sort/class values correctly_parse_cardsextracts short IDs, names, posters, and remarkshomeVideoContentmerges homepage movie/tv sections and truncates result sizecategoryContentrequests the expected PPnix listing URL and returns repo-style page payload withoutpagecountsearchContentrequests the expected PPnix search URL and filters valid movie/tv IDs_extract_m3u8_itemsreadsinfoidand ordered item names from inline JSdetailContentbuilds metadata andPPnixplay group from fixture HTMLplayerContentreturns direct m3u8 URL for a valid compact play IDplayerContentfalls back to parse-required response when play ID is malformed
Fixture design
Use embedded HTML snippets rather than live requests. Fixtures should cover:
- Homepage lists
- Category/search cards
- Detail metadata block
- Inline script with
infoidandm3u8
No network access will be used in tests.
Implementation Notes
- Prefer
BeautifulSoupbecause the project already depends on it and many spiders use HTML-string parsing without heavy DOM abstractions - Keep helper methods small and deterministic so test failures identify one parser responsibility at a time
- Avoid introducing caching or feature flags until they are required by a failing test