init project

This commit is contained in:
alphardex
2019-01-16 22:45:05 +08:00
commit d9f4136fa4
32 changed files with 1044 additions and 0 deletions

2
.coveragerc Normal file
View File

@@ -0,0 +1,2 @@
[run]
source = rsshub

3
.flaskenv Normal file
View File

@@ -0,0 +1,3 @@
FLASK_ENV=development
FLASK_APP=rsshub
FLASK_DEBUG=1

17
.gitignore vendored Normal file
View File

@@ -0,0 +1,17 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
.pytest_cache
# Distribution / packaging
build/
dist/
*.egg-info/
.idea
venv
# Others
.vscode
.coverage
htmlcov/
data-dev.db

26
Pipfile Normal file
View File

@@ -0,0 +1,26 @@
[[source]]
url = "https://pypi.doubanio.com/simple"
verify_ssl = true
name = "pypi"
[packages]
bootstrap-flask = "*"
flask-debugtoolbar = "*"
flask-moment = "*"
Flask = "*"
python-dotenv = "*"
"psycopg2" = "*"
gunicorn = "*"
requests = "*"
parsel = "*"
[dev-packages]
coverage = "*"
pdir2 = "*"
ptpython = "*"
yapf = "*"
pylint = "*"
[scripts]
test = "python -m unittest discover"
coverage = "coverage run -m unittest discover"

456
Pipfile.lock generated Normal file
View File

@@ -0,0 +1,456 @@
{
"_meta": {
"hash": {
"sha256": "1a952bf63349734b6daa3bc57851614425ebe4a867ca1826881a6b0324324c27"
},
"pipfile-spec": 6,
"requires": {},
"sources": [
{
"name": "pypi",
"url": "https://pypi.doubanio.com/simple",
"verify_ssl": true
}
]
},
"default": {
"blinker": {
"hashes": [
"sha256:471aee25f3992bd325afa3772f1063dbdbbca947a041b8b89466dc00d606f8b6"
],
"version": "==1.4"
},
"bootstrap-flask": {
"hashes": [
"sha256:1f7462261b8104687807ce74b397270e7ade07c491ad7d53f215940d8433d756",
"sha256:ce5cf19c46b8d385923dc2f9ca76b92fc08c1a8d7dcb5d177325a85a94d71045"
],
"index": "pypi",
"version": "==1.0.9"
},
"certifi": {
"hashes": [
"sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7",
"sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033"
],
"version": "==2018.11.29"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"click": {
"hashes": [
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
"sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
],
"version": "==7.0"
},
"cssselect": {
"hashes": [
"sha256:066d8bc5229af09617e24b3ca4d52f1f9092d9e061931f4184cd572885c23204",
"sha256:3b5103e8789da9e936a68d993b70df732d06b8bb9a337a05ed4eb52c17ef7206"
],
"version": "==1.0.3"
},
"flask": {
"hashes": [
"sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48",
"sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05"
],
"index": "pypi",
"version": "==1.0.2"
},
"flask-debugtoolbar": {
"hashes": [
"sha256:3d9657bc0c3633ace429e3ff451742bb59d1b7a7b95c9eb23a65ac9be2812959",
"sha256:ec810083123aae0632eb32ba11e1cb4cdace81e7ce6c5009dd06c5204afbce52"
],
"index": "pypi",
"version": "==0.10.1"
},
"flask-moment": {
"hashes": [
"sha256:71a601fcd5be4742227251641cb706c109680b54c5fb25c5d2ed96e576ec3b4d",
"sha256:af7ccd599d85e751ff1f7661904daa51df9950e9bc9bd4ccf174bd38ccbc401f"
],
"index": "pypi",
"version": "==0.6.0"
},
"gunicorn": {
"hashes": [
"sha256:aa8e0b40b4157b36a5df5e599f45c9c76d6af43845ba3b3b0efe2c70473c2471",
"sha256:fa2662097c66f920f53f70621c6c58ca4a3c4d3434205e608e121b5b3b71f4f3"
],
"index": "pypi",
"version": "==19.9.0"
},
"idna": {
"hashes": [
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
],
"version": "==2.8"
},
"itsdangerous": {
"hashes": [
"sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19",
"sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"
],
"version": "==1.1.0"
},
"jinja2": {
"hashes": [
"sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
"sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"
],
"version": "==2.10"
},
"lxml": {
"hashes": [
"sha256:0dd6589fa75d369ba06d2b5f38dae107f76ea127f212f6a7bee134f6df2d1d21",
"sha256:1afbac344aa68c29e81ab56c1a9411c3663157b5aee5065b7fa030b398d4f7e0",
"sha256:1baad9d073692421ad5dbbd81430aba6c7f5fdc347f03537ae046ddf2c9b2297",
"sha256:1d8736421a2358becd3edf20260e41a06a0bf08a560480d3a5734a6bcbacf591",
"sha256:1e1d9bddc5afaddf0de76246d3f2152f961697ad7439c559f179002682c45801",
"sha256:1f179dc8b2643715f020f4d119d5529b02cd794c1c8f305868b73b8674d2a03f",
"sha256:241fb7bdf97cb1df1edfa8f0bcdfd80525d4023dac4523a241907c8b2f44e541",
"sha256:2f9765ee5acd3dbdcdc0d0c79309e01f7c16bc8d39b49250bf88de7b46daaf58",
"sha256:312e1e1b1c3ce0c67e0b8105317323e12807955e8186872affb667dbd67971f6",
"sha256:3273db1a8055ca70257fd3691c6d2c216544e1a70b673543e15cc077d8e9c730",
"sha256:34dfaa8c02891f9a246b17a732ca3e99c5e42802416628e740a5d1cb2f50ff49",
"sha256:3aa3f5288af349a0f3a96448ebf2e57e17332d99f4f30b02093b7948bd9f94cc",
"sha256:51102e160b9d83c1cc435162d90b8e3c8c93b28d18d87b60c56522d332d26879",
"sha256:56115fc2e2a4140e8994eb9585119a1ae9223b506826089a3ba753a62bd194a6",
"sha256:69d83de14dbe8fe51dccfd36f88bf0b40f5debeac763edf9f8325180190eba6e",
"sha256:99fdce94aeaa3ccbdfcb1e23b34273605c5853aa92ec23d84c84765178662c6c",
"sha256:a7c0cd5b8a20f3093ee4a67374ccb3b8a126743b15a4d759e2a1bf098faac2b2",
"sha256:abe12886554634ed95416a46701a917784cb2b4c77bfacac6916681d49bbf83d",
"sha256:b4f67b5183bd5f9bafaeb76ad119e977ba570d2b0e61202f534ac9b5c33b4485",
"sha256:bdd7c1658475cc1b867b36d5c4ed4bc316be8d3368abe03d348ba906a1f83b0e",
"sha256:c6f24149a19f611a415a51b9bc5f17b6c2f698e0d6b41ffb3fa9f24d35d05d73",
"sha256:d1e111b3ab98613115a208c1017f266478b0ab224a67bc8eac670fa0bad7d488",
"sha256:d6520aa965773bbab6cb7a791d5895b00d02cf9adc93ac2bf4edb9ac1a6addc5",
"sha256:dd185cde2ccad7b649593b0cda72021bc8a91667417001dbaf24cd746ecb7c11",
"sha256:de2e5b0828a9d285f909b5d2e9d43f1cf6cf21fe65bc7660bdaa1780c7b58298",
"sha256:f726444b8e909c4f41b4fde416e1071cf28fa84634bfb4befdf400933b6463af"
],
"version": "==4.3.0"
},
"markupsafe": {
"hashes": [
"sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432",
"sha256:130f844e7f5bdd8e9f3f42e7102ef1d49b2e6fdf0d7526df3f87281a532d8c8b",
"sha256:19f637c2ac5ae9da8bfd98cef74d64b7e1bb8a63038a3505cd182c3fac5eb4d9",
"sha256:1b8a7a87ad1b92bd887568ce54b23565f3fd7018c4180136e1cf412b405a47af",
"sha256:1c25694ca680b6919de53a4bb3bdd0602beafc63ff001fea2f2fc16ec3a11834",
"sha256:1f19ef5d3908110e1e891deefb5586aae1b49a7440db952454b4e281b41620cd",
"sha256:1fa6058938190ebe8290e5cae6c351e14e7bb44505c4a7624555ce57fbbeba0d",
"sha256:31cbb1359e8c25f9f48e156e59e2eaad51cd5242c05ed18a8de6dbe85184e4b7",
"sha256:3e835d8841ae7863f64e40e19477f7eb398674da6a47f09871673742531e6f4b",
"sha256:4e97332c9ce444b0c2c38dd22ddc61c743eb208d916e4265a2a3b575bdccb1d3",
"sha256:525396ee324ee2da82919f2ee9c9e73b012f23e7640131dd1b53a90206a0f09c",
"sha256:52b07fbc32032c21ad4ab060fec137b76eb804c4b9a1c7c7dc562549306afad2",
"sha256:52ccb45e77a1085ec5461cde794e1aa037df79f473cbc69b974e73940655c8d7",
"sha256:5c3fbebd7de20ce93103cb3183b47671f2885307df4a17a0ad56a1dd51273d36",
"sha256:5e5851969aea17660e55f6a3be00037a25b96a9b44d2083651812c99d53b14d1",
"sha256:5edfa27b2d3eefa2210fb2f5d539fbed81722b49f083b2c6566455eb7422fd7e",
"sha256:7d263e5770efddf465a9e31b78362d84d015cc894ca2c131901a4445eaa61ee1",
"sha256:83381342bfc22b3c8c06f2dd93a505413888694302de25add756254beee8449c",
"sha256:857eebb2c1dc60e4219ec8e98dfa19553dae33608237e107db9c6078b1167856",
"sha256:98e439297f78fca3a6169fd330fbe88d78b3bb72f967ad9961bcac0d7fdd1550",
"sha256:bf54103892a83c64db58125b3f2a43df6d2cb2d28889f14c78519394feb41492",
"sha256:d9ac82be533394d341b41d78aca7ed0e0f4ba5a2231602e2f05aa87f25c51672",
"sha256:e982fe07ede9fada6ff6705af70514a52beb1b2c3d25d4e873e82114cf3c5401",
"sha256:edce2ea7f3dfc981c4ddc97add8a61381d9642dc3273737e756517cc03e84dd6",
"sha256:efdc45ef1afc238db84cb4963aa689c0408912a0239b0721cb172b4016eb31d6",
"sha256:f137c02498f8b935892d5c0172560d7ab54bc45039de8805075e19079c639a9c",
"sha256:f82e347a72f955b7017a39708a3667f106e6ad4d10b25f237396a7115d8ed5fd",
"sha256:fb7c206e01ad85ce57feeaaa0bf784b97fa3cad0d4a5737bc5295785f5c613a1"
],
"version": "==1.1.0"
},
"parsel": {
"hashes": [
"sha256:493a9214acbdcb4487a084d95344c25e85e90426a67311ea0425dc5df8dc24b9",
"sha256:9ccd82b8a122345601f6f9209e972c0e8c3518a188fcff2d37cb4d7bc570b4b8"
],
"index": "pypi",
"version": "==1.5.1"
},
"psycopg2": {
"hashes": [
"sha256:10e391687b171878181e71736d0effe3772314a339d9ae30995ec8171a0c834e",
"sha256:1283f9d45e458c2dcb15ba89367923563f90ef636fe78ee22df75183484a0237",
"sha256:1a9c32e4d140bea225f9821d993b2e53c913e717ea97b851246aa9b300095d8f",
"sha256:1be6f2438d2b71fec7b07c3c0949dd321b04349c382907ea76b36120edec8300",
"sha256:20ca6f29e118b8dd7133e8708b3fba2881e70a4e0841f874ed23985b7201a076",
"sha256:227c115b3c1f65d61385e51ac690b91b584640aefb45bffacd4bd33d02ed7221",
"sha256:27959abe64ca1fc6d8cd11a71a1f421d8287831a3262bd4cacd43bbf43cc3c82",
"sha256:2b2daf1fe30a58300542aea679fd87d1e1c2afd36e7644837b7954fa2dbacb92",
"sha256:36e51a51f295fdf67bcf05e7b1877011a6b39e6622b0013fe31c5025241873a3",
"sha256:3992b9b914f2eb77dc07e8045d2ca979e491612808bc5c7cd68f307469acf9f6",
"sha256:39a11de2335ad45ececed43ab851d36a4c52843d756471b940804f301792781e",
"sha256:3c2afe9ef0d1649005e3ccf93c1aaccd6f8ee379530e763d3b3b77f406b7c0ae",
"sha256:3fb18e0e52807fe3a300dc1b5421aa492d5e759550918f597d61863419482535",
"sha256:55eab94de96ee9702f23283e9c8b03cfdb0001e2b14d5d2e1bd5ff8114b96b9f",
"sha256:7e95c0ab7e7e6e452586f35d4d8966b1e924c8dd2c23977e3ea4968770ff1d26",
"sha256:7f47514dbddf604f196fcfe5da955537f04691bef8124aff5632316a78d992b7",
"sha256:8345370356bb4bddf93acbcfd0357163dd6b09471937adcfb38a2fbb49bdce53",
"sha256:8bc6ecb220c0b88d3742042013129c817c44459795c97e9ce1bca70a3f37a53b",
"sha256:8df623f248be15d1725faf5f333791678775047f12f17a90d29b5d22573f5cdc",
"sha256:9645f1305e4268cc0fc88c823cd6c91de27c003e183c233a6a230e5e963039ee",
"sha256:a68719ed5be8373dd72c9e45d55f7a202285e05a2e392eaa8872a67ea47d7d20",
"sha256:aca0edf062ec09e954fdf0cc93d3a872362701210983a1442549e703aedec25d",
"sha256:b0dd2114d93d8f424bb8ae76e0dc540f104b70ca9163172c05e7700b1459d4c9",
"sha256:b2c09359d6802279efb9efb3f91a9c94567151baee95175f9b637ea628f35244",
"sha256:ca7bc37b1efb7cc25271bf10f398462ed975d95259af1406d38fcb268466e34f",
"sha256:e64235d9013ebf6319cb9654e08f5066112c34d8c4cc41186254ab9c3d6d5b9b",
"sha256:ec9be679c0065667503851141c31fa699e1cc69ded3ba8e5d3673dd5a6eb1370",
"sha256:eca00d0f91fcb44d88b12f1fd16ad138e38fa07debb79587e2b7ff1fe80d72b9",
"sha256:f256e807b8b2b45b6af60d7f2bb5194aab2f4acc861241c4d8ef942a55f5030d",
"sha256:fce7612a3bd6a7ba95799f88285653bf130bd7ca066b52674d5f850108b2aec0"
],
"index": "pypi",
"version": "==2.7.6.1"
},
"python-dotenv": {
"hashes": [
"sha256:a84569d0e00d178bc5b957f7ff208bf49287cbf61857c31c258c4a91f571527b",
"sha256:c9b1ddd3cdbe75c7d462cb84674d87130f4b948f090f02c7d7144779afb99ae0"
],
"index": "pypi",
"version": "==0.10.1"
},
"requests": {
"hashes": [
"sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e",
"sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b"
],
"index": "pypi",
"version": "==2.21.0"
},
"six": {
"hashes": [
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
"sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
],
"version": "==1.12.0"
},
"urllib3": {
"hashes": [
"sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39",
"sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22"
],
"version": "==1.24.1"
},
"w3lib": {
"hashes": [
"sha256:67eacd24f4f233a80bc472835ad6aad99616e49aa1d795f3dbb7b8b028fec6cf",
"sha256:e54db912ae1c79d3e246b16d3645bdf701f70ac4f8a2fc46eeb284cc02a31cd7"
],
"version": "==1.20.0"
},
"werkzeug": {
"hashes": [
"sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c",
"sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b"
],
"version": "==0.14.1"
}
},
"develop": {
"astroid": {
"hashes": [
"sha256:35b032003d6a863f5dcd7ec11abd5cd5893428beaa31ab164982403bcb311f22",
"sha256:6a5d668d7dc69110de01cdf7aeec69a679ef486862a0850cc0fd5571505b6b7e"
],
"version": "==2.1.0"
},
"colorama": {
"hashes": [
"sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d",
"sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"
],
"markers": "platform_system == 'Windows'",
"version": "==0.4.1"
},
"coverage": {
"hashes": [
"sha256:09e47c529ff77bf042ecfe858fb55c3e3eb97aac2c87f0349ab5a7efd6b3939f",
"sha256:0a1f9b0eb3aa15c990c328535655847b3420231af299386cfe5efc98f9c250fe",
"sha256:0cc941b37b8c2ececfed341444a456912e740ecf515d560de58b9a76562d966d",
"sha256:10e8af18d1315de936d67775d3a814cc81d0747a1a0312d84e27ae5610e313b0",
"sha256:1b4276550b86caa60606bd3572b52769860a81a70754a54acc8ba789ce74d607",
"sha256:1e8a2627c48266c7b813975335cfdea58c706fe36f607c97d9392e61502dc79d",
"sha256:2b224052bfd801beb7478b03e8a66f3f25ea56ea488922e98903914ac9ac930b",
"sha256:447c450a093766744ab53bf1e7063ec82866f27bcb4f4c907da25ad293bba7e3",
"sha256:46101fc20c6f6568561cdd15a54018bb42980954b79aa46da8ae6f008066a30e",
"sha256:4710dc676bb4b779c4361b54eb308bc84d64a2fa3d78e5f7228921eccce5d815",
"sha256:510986f9a280cd05189b42eee2b69fecdf5bf9651d4cd315ea21d24a964a3c36",
"sha256:5535dda5739257effef56e49a1c51c71f1d37a6e5607bb25a5eee507c59580d1",
"sha256:5a7524042014642b39b1fcae85fb37556c200e64ec90824ae9ecf7b667ccfc14",
"sha256:5f55028169ef85e1fa8e4b8b1b91c0b3b0fa3297c4fb22990d46ff01d22c2d6c",
"sha256:6694d5573e7790a0e8d3d177d7a416ca5f5c150742ee703f3c18df76260de794",
"sha256:6831e1ac20ac52634da606b658b0b2712d26984999c9d93f0c6e59fe62ca741b",
"sha256:77f0d9fa5e10d03aa4528436e33423bfa3718b86c646615f04616294c935f840",
"sha256:828ad813c7cdc2e71dcf141912c685bfe4b548c0e6d9540db6418b807c345ddd",
"sha256:85a06c61598b14b015d4df233d249cd5abfa61084ef5b9f64a48e997fd829a82",
"sha256:8cb4febad0f0b26c6f62e1628f2053954ad2c555d67660f28dfb1b0496711952",
"sha256:a5c58664b23b248b16b96253880b2868fb34358911400a7ba39d7f6399935389",
"sha256:aaa0f296e503cda4bc07566f592cd7a28779d433f3a23c48082af425d6d5a78f",
"sha256:ab235d9fe64833f12d1334d29b558aacedfbca2356dfb9691f2d0d38a8a7bfb4",
"sha256:b3b0c8f660fae65eac74fbf003f3103769b90012ae7a460863010539bb7a80da",
"sha256:bab8e6d510d2ea0f1d14f12642e3f35cefa47a9b2e4c7cea1852b52bc9c49647",
"sha256:c45297bbdbc8bb79b02cf41417d63352b70bcb76f1bbb1ee7d47b3e89e42f95d",
"sha256:d19bca47c8a01b92640c614a9147b081a1974f69168ecd494687c827109e8f42",
"sha256:d64b4340a0c488a9e79b66ec9f9d77d02b99b772c8b8afd46c1294c1d39ca478",
"sha256:da969da069a82bbb5300b59161d8d7c8d423bc4ccd3b410a9b4d8932aeefc14b",
"sha256:ed02c7539705696ecb7dc9d476d861f3904a8d2b7e894bd418994920935d36bb",
"sha256:ee5b8abc35b549012e03a7b1e86c09491457dba6c94112a2482b18589cc2bdb9"
],
"index": "pypi",
"version": "==4.5.2"
},
"docopt": {
"hashes": [
"sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"
],
"version": "==0.6.2"
},
"isort": {
"hashes": [
"sha256:1153601da39a25b14ddc54955dbbacbb6b2d19135386699e2ad58517953b34af",
"sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8",
"sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497"
],
"version": "==4.3.4"
},
"jedi": {
"hashes": [
"sha256:571702b5bd167911fe9036e5039ba67f820d6502832285cde8c881ab2b2149fd",
"sha256:c8481b5e59d34a5c7c42e98f6625e633f6ef59353abea6437472c7ec2093f191"
],
"version": "==0.13.2"
},
"lazy-object-proxy": {
"hashes": [
"sha256:0ce34342b419bd8f018e6666bfef729aec3edf62345a53b537a4dcc115746a33",
"sha256:1b668120716eb7ee21d8a38815e5eb3bb8211117d9a90b0f8e21722c0758cc39",
"sha256:209615b0fe4624d79e50220ce3310ca1a9445fd8e6d3572a896e7f9146bbf019",
"sha256:27bf62cb2b1a2068d443ff7097ee33393f8483b570b475db8ebf7e1cba64f088",
"sha256:27ea6fd1c02dcc78172a82fc37fcc0992a94e4cecf53cb6d73f11749825bd98b",
"sha256:2c1b21b44ac9beb0fc848d3993924147ba45c4ebc24be19825e57aabbe74a99e",
"sha256:2df72ab12046a3496a92476020a1a0abf78b2a7db9ff4dc2036b8dd980203ae6",
"sha256:320ffd3de9699d3892048baee45ebfbbf9388a7d65d832d7e580243ade426d2b",
"sha256:50e3b9a464d5d08cc5227413db0d1c4707b6172e4d4d915c1c70e4de0bbff1f5",
"sha256:5276db7ff62bb7b52f77f1f51ed58850e315154249aceb42e7f4c611f0f847ff",
"sha256:61a6cf00dcb1a7f0c773ed4acc509cb636af2d6337a08f362413c76b2b47a8dd",
"sha256:6ae6c4cb59f199d8827c5a07546b2ab7e85d262acaccaacd49b62f53f7c456f7",
"sha256:7661d401d60d8bf15bb5da39e4dd72f5d764c5aff5a86ef52a042506e3e970ff",
"sha256:7bd527f36a605c914efca5d3d014170b2cb184723e423d26b1fb2fd9108e264d",
"sha256:7cb54db3535c8686ea12e9535eb087d32421184eacc6939ef15ef50f83a5e7e2",
"sha256:7f3a2d740291f7f2c111d86a1c4851b70fb000a6c8883a59660d95ad57b9df35",
"sha256:81304b7d8e9c824d058087dcb89144842c8e0dea6d281c031f59f0acf66963d4",
"sha256:933947e8b4fbe617a51528b09851685138b49d511af0b6c0da2539115d6d4514",
"sha256:94223d7f060301b3a8c09c9b3bc3294b56b2188e7d8179c762a1cda72c979252",
"sha256:ab3ca49afcb47058393b0122428358d2fbe0408cf99f1b58b295cfeb4ed39109",
"sha256:bd6292f565ca46dee4e737ebcc20742e3b5be2b01556dafe169f6c65d088875f",
"sha256:cb924aa3e4a3fb644d0c463cad5bc2572649a6a3f68a7f8e4fbe44aaa6d77e4c",
"sha256:d0fc7a286feac9077ec52a927fc9fe8fe2fabab95426722be4c953c9a8bede92",
"sha256:ddc34786490a6e4ec0a855d401034cbd1242ef186c20d79d2166d6a4bd449577",
"sha256:e34b155e36fa9da7e1b7c738ed7767fc9491a62ec6af70fe9da4a057759edc2d",
"sha256:e5b9e8f6bda48460b7b143c3821b21b452cb3a835e6bbd5dd33aa0c8d3f5137d",
"sha256:e81ebf6c5ee9684be8f2c87563880f93eedd56dd2b6146d8a725b50b7e5adb0f",
"sha256:eb91be369f945f10d3a49f5f9be8b3d0b93a4c2be8f8a5b83b0571b8123e0a7a",
"sha256:f460d1ceb0e4a5dcb2a652db0904224f367c9b3c1470d5a7683c0480e582468b"
],
"version": "==1.3.1"
},
"mccabe": {
"hashes": [
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
],
"version": "==0.6.1"
},
"parso": {
"hashes": [
"sha256:35704a43a3c113cce4de228ddb39aab374b8004f4f2407d070b6a2ca784ce8a2",
"sha256:895c63e93b94ac1e1690f5fdd40b65f07c8171e3e53cbd7793b5b96c0e0a7f24"
],
"version": "==0.3.1"
},
"pdir2": {
"hashes": [
"sha256:71761d0fe879de2c91fc36ecdb9a7f20d059deee7d94d0f10c2dea2cca1c1929",
"sha256:f3c08df2387e6d34f2ce22be78de9dd809fcffae96a69be733a4e99a2b5d8872"
],
"index": "pypi",
"version": "==0.3.1.post2"
},
"prompt-toolkit": {
"hashes": [
"sha256:c1d6aff5252ab2ef391c2fe498ed8c088066f66bc64a8d5c095bbf795d9fec34",
"sha256:d4c47f79b635a0e70b84fdb97ebd9a274203706b1ee5ed44c10da62755cf3ec9",
"sha256:fd17048d8335c1e6d5ee403c3569953ba3eb8555d710bfc548faf0712666ea39"
],
"version": "==2.0.7"
},
"ptpython": {
"hashes": [
"sha256:51a74abe931f692360a32d650c2ba1ca329c08f3ed9b1de8abcd1164e0b0a6a7",
"sha256:938ee050e37d61c138dbbeb21383dfef8b9ed4ffb453a5f34041f42025bf5042",
"sha256:ebe9d68ea7532ec8ab306d4bdc7ec393701cd9bbd6eff0aa3067c821f99264d4"
],
"index": "pypi",
"version": "==2.0.4"
},
"pygments": {
"hashes": [
"sha256:5ffada19f6203563680669ee7f53b64dabbeb100eb51b61996085e99c03b284a",
"sha256:e8218dd399a61674745138520d0d4cf2621d7e032439341bc3f647bff125818d"
],
"version": "==2.3.1"
},
"pylint": {
"hashes": [
"sha256:689de29ae747642ab230c6d37be2b969bf75663176658851f456619aacf27492",
"sha256:771467c434d0d9f081741fec1d64dfb011ed26e65e12a28fe06ca2f61c4d556c"
],
"index": "pypi",
"version": "==2.2.2"
},
"six": {
"hashes": [
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
"sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
],
"version": "==1.12.0"
},
"wcwidth": {
"hashes": [
"sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e",
"sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"
],
"version": "==0.1.7"
},
"wrapt": {
"hashes": [
"sha256:e03f19f64d81d0a3099518ca26b04550026f131eced2e76ced7b85c6b8d32128"
],
"version": "==1.11.0"
},
"yapf": {
"hashes": [
"sha256:8aa7f9abdb97b4da4d3227306b88477982daafef0a96cc41639754ca31f46d55",
"sha256:f2df5891481f94ddadfbf8ae8ae499080752cfb06005a31bbb102f3012f8b944"
],
"index": "pypi",
"version": "==0.25.0"
}
}
}

1
Procfile Normal file
View File

@@ -0,0 +1 @@
web: flask db upgrade; gunicorn wsgi:app

53
README.md Normal file
View File

@@ -0,0 +1,53 @@
# RSSHub
> 🍰 万物皆可 RSS
RSSHub 是一个轻量、易于扩展的 RSS 生成器,可以给任何奇奇怪怪的内容生成 RSS 订阅源
本项目是[原RSSHub](https://github.com/DIYgod/RSSHub)的Python实现。
*其实用Python写爬虫要比JS更方便:p*
DEMO地址https://rsshub-python.herokuapp.com
## RSS过滤
你可以通过以下查询字符串来过滤RSS的内容
- include_title: 搜索标题
- include_description: 搜索描述
- exclude_title: 排除标题
- exclude_description: 排除描述
- limit: 限制条数
## 贡献RSS方法
1. fork这份仓库
2. 在spiders文件夹下创建新的爬虫目录和脚本编写爬虫参考我的[爬虫教程](https://alphardex.github.io/2018/12/15/%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E7%B2%BE%E8%A6%81/)
3. 在blueprints的main.py中添加对应的路由按照之前路由的格式
4. 提pr
## 部署
### 搭建
首先确保安装了[pipenv](https://github.com/pypa/pipenv)
``` bash
git clone https://github.com/alphardex/rsshub
cd rsshub
pipenv install --dev
pipenv shell
```
### 运行
``` bash
flask run
```
### 部署到Heroku
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/alphardex/rsshub)
记得在环境变量中把FLASK_CONFIG设为production

9
app.json Normal file
View File

@@ -0,0 +1,9 @@
{
"name": "RSSHub",
"description": "A rsshub powered by flask.",
"repository": "https://github.com/alphardex/rsshub",
"keywords": [
"python",
"flask"
]
}

61
rsshub/__init__.py Normal file
View File

@@ -0,0 +1,61 @@
import os
import click
from flask import Flask, render_template
from flask.cli import with_appcontext
from rsshub.config import config
from rsshub.extensions import *
from rsshub.blueprints.main import bp as main_bp
from rsshub.utils import XMLResponse
def create_app(config_name=None):
if config_name is None:
config_name = os.getenv('FLASK_CONFIG', 'development')
app = Flask(__name__)
app.config.from_object(config[config_name])
app.response_class = XMLResponse
register_blueprints(app)
register_extensions(app)
register_errors(app)
register_cli(app)
return app
def register_extensions(app):
bootstrap.init_app(app)
debugtoolbar.init_app(app)
moment.init_app(app)
def register_blueprints(app):
app.register_blueprint(main_bp)
def register_errors(app):
@app.errorhandler(400)
def bad_request(e):
return render_template('errors/400.html'), 400
@app.errorhandler(404)
def page_not_found(e):
return render_template('errors/404.html'), 404
@app.errorhandler(500)
def internal_server_error(e):
return render_template('errors/500.html'), 500
def register_cli(app):
@app.cli.command()
@with_appcontext
def ptshell():
"""Use ptpython as shell."""
try:
from ptpython.repl import embed
if not app.config['TESTING']:
embed(app.make_shell_context())
except ImportError:
click.echo('ptpython not installed! Use the default shell instead.')

View File

46
rsshub/blueprints/main.py Normal file
View File

@@ -0,0 +1,46 @@
from flask import Blueprint, render_template, current_app, request
bp = Blueprint('main', __name__)
@bp.route('/')
def index():
return render_template('main/index.html')
@bp.route('/feeds')
def feeds():
feed_rules = [rule for rule in current_app.url_map._rules if 'main' in rule.endpoint][:-2]
return render_template('main/feeds.html', rules=feed_rules)
@bp.app_template_global()
def filter_content(ctx):
include_title = request.args.get('include_title')
include_description = request.args.get('include_description')
exclude_title = request.args.get('exclude_title')
exclude_description = request.args.get('exclude_description')
limit = request.args.get('limit', type=int)
items = ctx['items'].copy()
items = [item for item in items if include_title in item['title']] if include_title else items
items = [item for item in items if include_description in item['description']] if include_description else items
items = [item for item in items if exclude_title not in item['title']] if exclude_title else items
items = [item for item in items if exclude_description not in item['description']] if exclude_description else items
items = items[:limit] if limit else items
ctx = ctx.copy()
ctx['items'] = items
return ctx
#---------- feed路由从这里开始 -----------#
@bp.route('/guokr/scentific')
def guokr_scientific():
from rsshub.spiders.guokr.scientific import ctx
return render_template('main/atom.xml', **filter_content(ctx))
@bp.route('/toutiao/today')
def toutiao_today():
from rsshub.spiders.toutiao.today import ctx
return render_template('main/atom.xml', **filter_content(ctx))

32
rsshub/config.py Normal file
View File

@@ -0,0 +1,32 @@
import os
import sys
basedir = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
class BaseConfig:
SITE_NAME = 'RSSHub'
GITHUB_USERNAME = 'alphardex'
EMAIL = '2582347430@qq.com'
SECRET_KEY = os.environ.get('SECRET_KEY') or 'f43hrt53et53'
DEBUG_TB_INTERCEPT_REDIRECTS = False
class DevelopmentConfig(BaseConfig):
pass
class TestingConfig(BaseConfig):
TESTING = True
class ProductionConfig(BaseConfig):
pass
config = {
'development': DevelopmentConfig,
'testing': TestingConfig,
'production': ProductionConfig
}

8
rsshub/extensions.py Normal file
View File

@@ -0,0 +1,8 @@
from flask_bootstrap import Bootstrap
from flask_debugtoolbar import DebugToolbarExtension
from flask_moment import Moment
bootstrap = Bootstrap()
debugtoolbar = DebugToolbarExtension()
moment = Moment()

View File

View File

@@ -0,0 +1,23 @@
import requests
data = requests.get(
'https://www.guokr.com/apis/minisite/article.json?retrieve_type=by_subject'
).json()['result']
def parse(d):
item = {}
item['title'] = d['title']
item['description'] = f"{d['summary']}<br><img referrerpolicy='no-referrer' src={d['image_info']['url']}"
item['pubDate'] = d['date_published']
item['link'] = d['url']
return item
ctx = {
'title': '果壳网 科学人',
'link': 'https://www.guokr.com/scientific',
'description': '果壳网 科学人',
'author': 'alphardex',
'items': list(map(parse, data))
}

View File

@@ -0,0 +1,22 @@
from rsshub.utils import fetch
base_url = 'https://toutiao.io'
tree = fetch(base_url)
items = []
posts = tree.css('.posts .post')
for post in posts:
item = {}
item['title'] = post.css('.title a::attr(title)').extract_first()
item['description'] = ''
item['pubDate'] = ''
item['link'] = base_url + post.css('.title a::attr(href)').extract_first()
items.append(item)
ctx = {
'title': '开发者头条:今日头条',
'link': 'https://toutiao.io',
'description': '开发者头条:今日头条',
'author': 'alphardex',
'items': items
}

View File

@@ -0,0 +1,48 @@
/* global */
nav {
margin-bottom: 30px;
}
.jumbotron {
margin-top: 20px;
padding-top: 38px;
padding-bottom: 38px;
}
.tip {
/* from github.com */
position: relative;
padding: 40px;
text-align: center;
background-color: #fafbfc;
border: 1px solid #e1e4e8;
border-radius: 3px;
box-shadow: inset 0 0 10px rgba(27, 31, 35, 0.05);
}
.hide {
display: none;
}
.inline {
display: inline;
}
.page-header {
padding-top: 20px;
padding-bottom: 20px;
}
.page-footer {
padding-top: 40px;
}
/* footer */
footer {
margin: 30px 0;
padding: 20px 0;
border-top: 1px solid #e5e5e5;
}

BIN
rsshub/static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@@ -0,0 +1,9 @@
{% extends 'layout.html' %}
{% block title %}400 错误{% endblock %}
{% block content %}
<div class="jumbotron">
<h1>400 Bad Request</h1>
</div>
{% endblock %}

View File

@@ -0,0 +1,9 @@
{% extends 'layout.html' %}
{% block title %}404 错误{% endblock %}
{% block content %}
<div class="jumbotron">
<h1>404 Not Found</h1>
</div>
{% endblock %}

View File

@@ -0,0 +1,9 @@
{% extends 'layout.html' %}
{% block title %}500 错误{% endblock %}
{% block content %}
<div class="jumbotron">
<h1>服务器出错</h1>
</div>
{% endblock %}

View File

@@ -0,0 +1,69 @@
{% from 'bootstrap/nav.html' import render_nav_item %}
<!DOCTYPE html>
<html>
<head>
{% block head %}
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>{% block title %}{% endblock title %}</title>
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
{% block styles %}
{{ bootstrap.load_css() }}
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<link href="https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
{% endblock styles %}
{% endblock head %}
</head>
<body>
{% block nav %}
<nav class="navbar navbar-expand-lg navbar-dark bg-dark" role="navigation">
<div class="container">
<a class="navbar-brand" href="{{url_for('main.index')}}">{{ config['SITE_NAME'] }}</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown"
aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavDropdown">
<ul class="nav navbar-nav ml-auto">
</ul>
</div>
</div>
</nav>
{% endblock nav %}
<main class="container">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-dismissable alert-{{ category }}" role="alert">
<button type="button" class="close" data-dismiss="alert">&times;</button>
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
{% block footer %}
<footer>
<p class="float-left">
<small>&copy;
Made by <a href="https://github.com/{{config['GITHUB_USERNAME']}}" target="_blank">{{config['GITHUB_USERNAME']}}</a>
with <span class="fa fa-heart"></span>
</small>
</p>
<p class="float-right">
<small><a href="mailto:{{ config['EMAIL'] }}">Contact</a></small>
</p>
</footer>
{% endblock footer %}
</main>
{% block scripts %}
{{ bootstrap.load_js() }}
{{ moment.include_moment() }}
{{ moment.locale('zh-cn') }}
{% endblock %}
</body>
</html>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<generator>{{config['SITE_NAME']}}</generator>
<webMaster>{{config['EMAIL']}}</webMaster>
<language>zh-cn</language>
<id>{{link}}</id>
<title><![CDATA[{{title|safe}}]]></title>
<link href="{{link}}"/>
<author>
<name><![CDATA[{{author|safe}}]]></name>
</author>
{% for item in items %}
<entry>
<id>{{item.link}}</id>
<title><![CDATA[{{item.title|safe}}]]></title>
<published>{{item.pubDate}}</published>
<updated>{{item.pubDate}}</updated>
<link href="{{item.link}}"/>
<content type="html" src="{{item.link}}"><![CDATA[{{item.description|safe}}]]></content>
</entry>
{% endfor %}
</feed>

View File

@@ -0,0 +1,11 @@
{% extends "layout.html" %}
{% block title %}All Feeds{% endblock title %}
{% block content %}
<div class="list-group">
{% for rule in rules %}
<a href="{{rule.rule}}" class="list-group-item list-group-item-action">{{rule.endpoint[5:]}}</a>
{% endfor %}
</div>
{% endblock content %}

View File

@@ -0,0 +1,13 @@
{% extends 'layout.html' %}
{% block title %}Welcome to RSShub!{% endblock title %}
{% block content %}
<div class="jumbotron">
<h1 class="display-4">Welcome to RSSHub!</h1>
<p class="lead">If you see this page, the RSSHub is successfully installed and working.</p>
<p>
<a class="btn btn-primary btn-lg" role="button" href="{{url_for('main.feeds')}}">View All Feeds</a>
</p>
</div>
{% endblock %}

25
rsshub/utils.py Normal file
View File

@@ -0,0 +1,25 @@
from flask import Response
import requests
from parsel import Selector
DEFAULT_HEADERS = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36'}
class XMLResponse(Response):
def __init__(self, response, **kwargs):
if 'mimetype' not in kwargs and 'contenttype' not in kwargs:
if response.startswith('<?xml'):
kwargs['mimetype'] = 'application/xml'
return super().__init__(response, **kwargs)
def fetch(url: str, headers: dict=DEFAULT_HEADERS, proxies: dict=None):
try:
res = requests.get(url, headers=headers, proxies=proxies)
res.raise_for_status()
except Exception as e:
print(f'[Err] {e}')
else:
html = res.text
tree = Selector(text=html)
return tree

0
tests/__init__.py Normal file
View File

15
tests/base.py Normal file
View File

@@ -0,0 +1,15 @@
import unittest
from flask import url_for
from rsshub import create_app
class BaseTestCase(unittest.TestCase):
def setUp(self):
app = create_app('testing')
self.context = app.test_request_context()
self.context.push()
self.client = app.test_client()
self.runner = app.test_cli_runner()
def tearDown(self):
self.context.pop()

7
tests/test_cli.py Normal file
View File

@@ -0,0 +1,7 @@
from tests.base import BaseTestCase
class CLITestCase(BaseTestCase):
def test_ptshell(self):
result = self.runner.invoke(args=['ptshell'])
self.assertNotIn('ptpython not installed! Use the default shell instead.', result.output)

30
tests/test_errors.py Normal file
View File

@@ -0,0 +1,30 @@
from flask import current_app, abort
from tests.base import BaseTestCase
class ErrorsTestCase(BaseTestCase):
def test_400(self):
@current_app.route('/400')
def bad_request():
abort(400)
response = self.client.get('/400')
data = response.get_data(as_text=True)
self.assertIn('400 Bad Request', data)
self.assertEqual(response.status_code, 400)
def test_404(self):
response = self.client.get('/nothing')
data = response.get_data(as_text=True)
self.assertIn('404 Not Found', data)
self.assertEqual(response.status_code, 404)
def test_500(self):
@current_app.route('/500')
def internal_server_error_for_test():
abort(500)
response = self.client.get('/500')
data = response.get_data(as_text=True)
self.assertIn('服务器出错', data)
self.assertEqual(response.status_code, 500)

8
tests/test_main.py Normal file
View File

@@ -0,0 +1,8 @@
from flask import url_for
from tests.base import BaseTestCase
class MainTestCase(BaseTestCase):
def test_index(self):
response = self.client.get(url_for('main.index'))
self.assertEqual(response.status_code, 200)

10
wsgi.py Normal file
View File

@@ -0,0 +1,10 @@
import os
from dotenv import load_dotenv
dotenv_path = os.path.join(os.path.dirname(__file__), '.env')
if os.path.exists(dotenv_path):
load_dotenv(dotenv_path)
from rsshub import create_app
app = create_app('production')